Compare commits
7
Commits
21234e38cd
...
ef947e5bb0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef947e5bb0 | ||
|
|
d00c76241c | ||
|
|
40ee9f1e83 | ||
|
|
9bc2e75fe5 | ||
|
|
1535481794 | ||
|
|
e34519113b | ||
|
|
2a8d34f85c |
@@ -0,0 +1,41 @@
|
||||
name: Set up Java and Gradle
|
||||
description: >-
|
||||
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.
|
||||
|
||||
# Wrapper validation is INSIDE this action now.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# `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
|
||||
steps:
|
||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
# 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' }}
|
||||
@@ -1,391 +0,0 @@
|
||||
# Current repository CI controls. This file lists only mechanisms and jobs that exist in this
|
||||
# checkout. Build/release supply-chain, image, signing, provenance, SBOM, and tag-release jobs are
|
||||
# intentionally absent until their later bounded reconstruction.
|
||||
#
|
||||
# Fields:
|
||||
# release_blocking: true, false, or conditional
|
||||
# mechanism: gradle-custom-task, gradle-plugin-task, contract-test, workflow-job,
|
||||
# or delegated-pending
|
||||
# ref: task, plugin@task, repository-relative test path below src/, or workflow job id
|
||||
# workflow/job: canonical workflow and job that execute or represent the control
|
||||
# execution: check (through Gradle check), explicit (named in the job), or job
|
||||
gates:
|
||||
- id: format-lint
|
||||
release_blocking: true
|
||||
mechanism: gradle-plugin-task
|
||||
ref: com.diffplug.spotless@spotlessCheck
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: unit-and-contract-tests
|
||||
release_blocking: true
|
||||
mechanism: gradle-plugin-task
|
||||
ref: java@test
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: conditional-transport-qualification
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: conditionalTransportQualification
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: explicit
|
||||
- id: clean-architecture-dependencies
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyCleanArchitectureDependencies
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: environment-contract
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyEnvKeys
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: documented-leaf-count
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyDocumentedLeafCount
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: declared-dependency-absence
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyDependencyPolicy
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: notification-api-surface
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyNotificationApiSurface
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: notification-configuration-contract
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyNotificationConfiguration
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: notification-support-grade-evidence
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyNotificationEvidence
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: runbook-reference-drift
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyRunbookReferences
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: graphql-api-surface
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyGraphQlApiSurface
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: mongo-api-surface
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyMongoApiSurface
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
# The strongest evidence this repository produces, and CI does not run it. Fifteen Compose lanes
|
||||
# start real PostgreSQL, MongoDB, Kafka, MinIO, Mailpit and Keycloak, take a real client-credentials
|
||||
# JWT, and prove things no in-JVM test can: that all-off boots with no external resource, that the
|
||||
# notification handoff delivers exactly once across a restart on the same volume, that the startup
|
||||
# log is silent. It runs from a developer's machine via scripts/run-compose-runtime-smoke.sh and
|
||||
# from nowhere else — no workflow invokes it, so nothing re-runs it on a pull request.
|
||||
#
|
||||
# Registered delegated-pending so the gap is a tracked absence rather than an unstated one.
|
||||
# Executing it in CI needs a Docker-capable runner and a decision about the minutes fifteen
|
||||
# container lanes cost, which is an infrastructure choice rather than a wiring oversight.
|
||||
- id: runtime-smoke-matrix
|
||||
release_blocking: conditional
|
||||
mechanism: delegated-pending
|
||||
ref: runtime-smoke-matrix-lane
|
||||
workflow: ci-quality-gates.yml
|
||||
job: release-gate
|
||||
execution: job
|
||||
# `conditional-transport-qualification` above is the registered GraphQL control, and it is a
|
||||
# boundary test: a @SpringBootTest over a nested test application with in-memory Basic Auth. Its own
|
||||
# javadoc says so — "the nested application deliberately owns only test authentication and CORS
|
||||
# policy". That is a legitimate transport-boundary proof and it is not release evidence for the
|
||||
# security posture, which is the distinction the Definition of Done draws.
|
||||
#
|
||||
# The real proof exists: the local-graphql Compose lane obtains a Keycloak client-credentials token
|
||||
# and posts it to /graphql on the running bootJar, asserting that anonymous and malformed
|
||||
# credentials are refused and the authenticated query answers. It is part of the runtime smoke
|
||||
# matrix above, so it inherits that control's pending status rather than having none of its own.
|
||||
- id: graphql-runtime-jwt
|
||||
release_blocking: conditional
|
||||
mechanism: delegated-pending
|
||||
ref: graphql-runtime-jwt-lane
|
||||
workflow: ci-quality-gates.yml
|
||||
job: release-gate
|
||||
execution: job
|
||||
- id: one-type-per-file
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyOneTypePerFile
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: readme-command-drift
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyReadmeCommands
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: trivy-suppression-governance
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyTrivyignore
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: quarantine-sunset
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyQuarantineSunset
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: public-path-snapshot
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyPublicPathSnapshot
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: explicit
|
||||
- id: dependency-locks
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyDependencyLocks
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: explicit
|
||||
- id: architecture-contract-test
|
||||
release_blocking: true
|
||||
mechanism: contract-test
|
||||
ref: app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
- id: sample-off
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: sampleOffTest
|
||||
workflow: ci-quality-gates.yml
|
||||
job: sample-off
|
||||
execution: explicit
|
||||
- id: gate-matrix-lint
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
ref: gate-matrix-lint
|
||||
workflow: ci-quality-gates.yml
|
||||
job: gate-matrix-lint
|
||||
execution: job
|
||||
- id: redis-sdk
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
ref: redis-sdk
|
||||
workflow: ci-quality-gates.yml
|
||||
job: redis-sdk
|
||||
execution: job
|
||||
- id: jpa-candidate-evidence
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
ref: jpa-candidate-evidence
|
||||
workflow: ci-quality-gates.yml
|
||||
job: jpa-candidate-evidence
|
||||
execution: job
|
||||
- id: jpa-r2-evidence
|
||||
release_blocking: conditional
|
||||
mechanism: workflow-job
|
||||
ref: jpa-r2-evidence
|
||||
workflow: jpa-r2-evidence.yml
|
||||
job: jpa-r2-evidence
|
||||
execution: job
|
||||
- id: quality-release-gate
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
ref: release-gate
|
||||
workflow: ci-quality-gates.yml
|
||||
job: release-gate
|
||||
execution: job
|
||||
- id: flaky-quarantine
|
||||
release_blocking: false
|
||||
mechanism: workflow-job
|
||||
ref: quarantine
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quarantine
|
||||
execution: job
|
||||
- id: dependency-review
|
||||
release_blocking: conditional
|
||||
mechanism: workflow-job
|
||||
ref: dependency-review
|
||||
workflow: dependency-vulnerability.yml
|
||||
job: dependency-review
|
||||
execution: job
|
||||
- id: dependency-submission
|
||||
release_blocking: false
|
||||
mechanism: workflow-job
|
||||
ref: dependency-submission
|
||||
workflow: dependency-vulnerability.yml
|
||||
job: dependency-submission
|
||||
execution: job
|
||||
- id: filesystem-vulnerability-scan
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
ref: trivy-fs
|
||||
workflow: dependency-vulnerability.yml
|
||||
job: trivy-fs
|
||||
execution: job
|
||||
- id: documentation-links
|
||||
release_blocking: conditional
|
||||
mechanism: workflow-job
|
||||
ref: lychee
|
||||
workflow: link-check.yml
|
||||
job: lychee
|
||||
execution: job
|
||||
- id: object-storage-minio-managed-contract
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: objectStorageMinioContractTest
|
||||
workflow: object-storage-qualification.yml
|
||||
job: minio-managed-contract
|
||||
execution: explicit
|
||||
- id: poster-image-migration
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: posterImageMigrationTest
|
||||
workflow: object-storage-qualification.yml
|
||||
job: poster-image-v7-migration
|
||||
execution: explicit
|
||||
- id: object-storage-minio-managed-fault
|
||||
release_blocking: conditional
|
||||
mechanism: gradle-custom-task
|
||||
ref: objectStorageMinioFaultTest
|
||||
workflow: object-storage-qualification.yml
|
||||
job: minio-managed-fault
|
||||
execution: explicit
|
||||
- id: object-storage-aws-protected-qualification
|
||||
release_blocking: conditional
|
||||
mechanism: delegated-pending
|
||||
ref: approval-gate-b
|
||||
workflow: object-storage-qualification.yml
|
||||
job: aws-managed-common-subset
|
||||
execution: job
|
||||
- id: redis-sdk-support-matrix
|
||||
release_blocking: true
|
||||
mechanism: contract-test
|
||||
ref: adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/RedisSupportMatrixTest.java
|
||||
workflow: ci-quality-gates.yml
|
||||
job: quality-gates
|
||||
execution: check
|
||||
# Promoted from delegated-pending: the workflow is no longer manual-only. A pull request that
|
||||
# touches the Redis leaf runs the standalone lane, and the full supported-version x topology
|
||||
# matrix runs nightly and on a release candidate. While it was dispatch-only, a release could
|
||||
# claim topology evidence that nobody had produced for that commit.
|
||||
- id: redis-sdk-topology-evidence
|
||||
release_blocking: conditional
|
||||
mechanism: workflow-job
|
||||
ref: topology-evidence
|
||||
workflow: redis-sdk-topology.yml
|
||||
job: topology-evidence
|
||||
execution: job
|
||||
- id: httpclient-stable-contract
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: httpClientStableContractTest
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
- id: httpclient-security-suite
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: httpClientSecurityTest
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
- id: httpclient-fault-injection
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: httpClientFailureInjectionTest
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
- id: httpclient-performance-certification
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: httpClientPerformanceTest
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
- id: httpclient-spring62-api-surface
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: spring62ApiSurfaceScan
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
# The 6.2 API-surface scan above proves the common packages compile against the older surface. It
|
||||
# does not prove they run on it, and the two were being conflated: a lane called
|
||||
# "spring62CompatibilityTest" reads as a runtime compatibility proof. The Gradle task is renamed to
|
||||
# say what it does, and the runtime claim is registered here as its own delegated-pending control
|
||||
# so the gap is a tracked absence rather than an unstated one. Executing it needs a Spring
|
||||
# Framework 6.2 distribution resolved into a separate test runtime, which this repository's
|
||||
# Boot 4.0 baseline does not carry.
|
||||
- id: httpclient-spring62-runtime
|
||||
release_blocking: conditional
|
||||
mechanism: delegated-pending
|
||||
ref: spring62-runtime-lane
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: job
|
||||
- id: httpclient-spring70-compatibility
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: spring70CompatibilityTest
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
- id: httpclient-documentation-drift
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
ref: httpclient-documentation
|
||||
workflow: httpclient-release.yml
|
||||
job: httpclient-documentation
|
||||
execution: job
|
||||
- id: httpclient-event-loop-blocking
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: httpClientBlockHoundTest
|
||||
workflow: httpclient-release.yml
|
||||
job: release-gate
|
||||
execution: explicit
|
||||
# The messaging platform's only claim that needs a real broker to be true. The gate is the
|
||||
# evidence check rather than the lane, and it depends on the lane: passing means both that every
|
||||
# fault scenario produced the outcome the shared contract fixes and that the committed manifest is
|
||||
# what this run wrote. Before it existed, `CertifiedEvidence` was a hand-authored list and
|
||||
# "certified against a live broker" was a sentence a developer could type.
|
||||
- id: messaging-broker-certification
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyMessagingCertificationEvidence
|
||||
workflow: messaging-certification.yml
|
||||
job: broker-certification
|
||||
execution: explicit
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,448 +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"
|
||||
# Deliberately a literal: a gate silently appearing or disappearing is the drift this lint exists to
|
||||
# catch, so growing the matrix is an explicit edit here. 38 as of the HTTP Client platform hardening,
|
||||
# which registered httpclient-spring62-runtime as a delegated-pending control — the 6.2 *runtime*
|
||||
# claim, distinct from the API-surface scan that was standing in for it. 40 after the Gradle
|
||||
# convention wave registered documented-leaf-count and declared-dependency-absence, then 46 after
|
||||
# the final qualification wave registered the four notification/runbook gates that existed but ran
|
||||
# nowhere and the two API-surface gates the convention had already wired into check. 48 once the
|
||||
# Compose runtime matrix and the GraphQL runtime JWT claim were registered as delegated-pending —
|
||||
# both are real and neither runs in CI. 49 once the messaging broker certification lane registered
|
||||
# its evidence gate — the first control in this repository whose subject is not "did the tests pass"
|
||||
# but "is the committed evidence what the run produced".
|
||||
readonly EXPECTED_GATE_COUNT=49
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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 != EXPECTED_GATE_COUNT )); then
|
||||
failures+=("matrix has ${total} gates; expected ${EXPECTED_GATE_COUNT}")
|
||||
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'
|
||||
@@ -1,756 +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' }}"
|
||||
# Workflow-lock update procedure (only after intentional review of the complete workflow 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
|
||||
# Replace this entire sorted array in the same reviewed change. Never refresh a single digest
|
||||
# merely to make this verifier pass.
|
||||
readonly EXPECTED_WORKFLOW_LOCK=(
|
||||
'e27d981f43815294e47470e51f61671ee7047794e2a638d1b71a91ee957a18c6 .github/workflows/ci-quality-gates.yml'
|
||||
'59de260a70c2c0a0d686d97035a189dc0567395977dfa18758f1a2d89d15a00d .github/workflows/dependency-vulnerability.yml'
|
||||
'1b3220c922f954500f727c6a799b24e4962915845b9248e8e496e5050e829f28 .github/workflows/fileserver-nightly.yml'
|
||||
'26812e16b8d6e4472543ddd49c7b16ee6b7697834ddbb653fa0424befd71c544 .github/workflows/fileserver-pr.yml'
|
||||
'86a240c4ce7d0d293616e30de30ed77bcfdc700fedb8916f083eda9567099096 .github/workflows/fileserver-release.yml'
|
||||
'58e28f3358d794ca08f4aa8df4516e03f50a9ee58488b3f0d2619998e069ef14 .github/workflows/httpclient-contract.yml'
|
||||
'823bc346e58a58b2c0814cd1e3e55ec90d360c138419ec3d8f05deb59c62c7eb .github/workflows/httpclient-nightly.yml'
|
||||
'ad84000efc438ee7439517b8f85819e62b13dab0aa4f94066c2905060f3bb581 .github/workflows/httpclient-release.yml'
|
||||
'3be84c9f15fa3b2ac5a085f8d725ec6d05e7007ae0b433da9e79b3bf340d57ea .github/workflows/jpa-next-hibernate8.yml'
|
||||
'a2b74bfb3af12d6d03cd2ea8a5e48490dd131afb89b79694d498c5798387ac53 .github/workflows/jpa-next-jpa4.yml'
|
||||
'cd955ef4af895df477896dad9577810f010b2beea8570b09b008f9e94e928bd0 .github/workflows/jpa-next-postgresql19.yml'
|
||||
'21e065880ef5d4c4ff973f52d8107ef08398ebaf9518ec6b2fd82d49c5d822c6 .github/workflows/jpa-nightly.yml'
|
||||
'04851f44ba94533bfbc8fabe2b3a2b408726a9996e86ed3864986d1499d16b50 .github/workflows/jpa-pr.yml'
|
||||
'59cb3a0ffc687a15eefe96bc5e3a70d42be78e1cc85d2e7f7880dac6124ca4c7 .github/workflows/jpa-r2-evidence.yml'
|
||||
'cf4f80134197dd6d7dc177f0d21294a6b9ffe8709be67d05089f7ff0ce6c9429 .github/workflows/jpa-release.yml'
|
||||
'5be7e931db749029d89787da042d6d7cf8e683d60698bd8a2993c29db26355fb .github/workflows/link-check.yml'
|
||||
'8adafc59a2d87a6c65ef94b4726d7d036ac81b150ed3d301578308e6f9a3523f .github/workflows/messaging-certification.yml'
|
||||
'4e4ccfa267ecd63b9369803d49f2dbdb2fa899517ad4cf23ab11d29104557a91 .github/workflows/notification-platform.yml'
|
||||
'64245586cd5936f1a5647b57f2cd9acd316f96fd75f713b1890decb812e7d5fe .github/workflows/object-storage-qualification.yml'
|
||||
'cbc104ea486c746229895e804e3be7716e056a02cce0588c537bce9f442f8b38 .github/workflows/redis-sdk-topology.yml'
|
||||
'89fb84532d542f7951e11cf2925425ea84b7ef9cc22f4587f1d2cfd99c481f5f .github/workflows/web-advanced-nightly.yml'
|
||||
'a3d01b73831f1f77a09edfe883e32cd63c8dc8c79b022faf7fec7bdd08c6e4db .github/workflows/web-advanced-release.yml'
|
||||
'4198ce8215097ae9342167c4985455bbe6e56956a3be05bee33428381cad638d .github/workflows/web-nightly.yml'
|
||||
'b07b92c43e94f674fe6c851603dd27bbed72894274f031b95c3d6e2b256650bd .github/workflows/web-pr.yml'
|
||||
'a82f3eacee01165cf9c0767657a584a2524d7bb751f1d567a696241ea44cb3b6 .github/workflows/web-release.yml'
|
||||
'f37b2b2598687679a3fb0ae9ea2b50cd5d84a64de7e5852f38a3e5b93bf76e4d .github/workflows/websocket-advanced-nightly.yml'
|
||||
'5643fe9c9d27d9e6f5ac30a731e77a962b68bed2961566e2e64cdb3991ef2350 .github/workflows/websocket-pr.yml'
|
||||
'c9fabc17fe755f9f0ee54007e48357fec9493a89ecf67fa2878f4dbc23478f30 .github/workflows/websocket-release.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"
|
||||
|
||||
[[ -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
|
||||
actual_workflow_lock=()
|
||||
while IFS= read -r -d '' locked_workflow; do
|
||||
locked_workflow_relative=${locked_workflow#"${REPOSITORY_ROOT}"/}
|
||||
if [[ -L "${locked_workflow}" || ! -f "${locked_workflow}" ]]; then
|
||||
locked_workflow_sha256='<invalid-file-type>'
|
||||
else
|
||||
locked_workflow_sha256=$(sha256sum -- "${locked_workflow}" | awk '{print $1}')
|
||||
fi
|
||||
actual_workflow_lock+=("${locked_workflow_sha256} ${locked_workflow_relative}")
|
||||
done < <(
|
||||
find "${WORKFLOWS_DIRECTORY}" -mindepth 1 -maxdepth 1 \
|
||||
\( -name '*.yml' -o -name '*.yaml' \) -print0 \
|
||||
| LC_ALL=C sort -z
|
||||
)
|
||||
|
||||
workflow_lock_entry_count=${#EXPECTED_WORKFLOW_LOCK[@]}
|
||||
if ((${#actual_workflow_lock[@]} > workflow_lock_entry_count)); then
|
||||
workflow_lock_entry_count=${#actual_workflow_lock[@]}
|
||||
fi
|
||||
for ((workflow_lock_index = 0; workflow_lock_index < workflow_lock_entry_count; workflow_lock_index++)); do
|
||||
expected_workflow_lock_entry=${EXPECTED_WORKFLOW_LOCK[workflow_lock_index]-<missing>}
|
||||
actual_workflow_lock_entry=${actual_workflow_lock[workflow_lock_index]-<missing>}
|
||||
if [[ "${actual_workflow_lock_entry}" != "${expected_workflow_lock_entry}" ]]; then
|
||||
printf 'gradle-wrapper-contract: workflow lock mismatch: expected %q; actual %q\n' \
|
||||
"${expected_workflow_lock_entry}" "${actual_workflow_lock_entry}" >&2
|
||||
workflow_lock_valid=0
|
||||
fi
|
||||
done
|
||||
|
||||
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: workflow set or bytes differ from the reviewed embedded manifest'
|
||||
|
||||
printf 'gradle-wrapper-contract: PASS\n'
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -36,23 +42,23 @@ jobs:
|
||||
echo "::error::${snapshot} exists locally but is not committed."
|
||||
exit 1
|
||||
fi
|
||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Check quality, public paths, and dependency locks
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
# `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 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
|
||||
@@ -61,80 +67,34 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
uses: ./.github/workflows/_reusable-gradle.yml
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- 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
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
# 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:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
# 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
|
||||
tasks: >-
|
||||
:shared-contract:edgeRateLimitContractTest
|
||||
:adapter:outbound:cache-redis:check
|
||||
:app-bootstrap:verifyEnvKeys
|
||||
verifyCleanArchitectureDependencies
|
||||
verifyEnvKeys
|
||||
verifyPublicPathSnapshot
|
||||
verifyConfigurationPropertiesProcessor
|
||||
--no-daemon --stacktrace
|
||||
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Produce zero-skip JPA candidate manifests
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -150,43 +110,32 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
uses: ./.github/workflows/_reusable-gradle.yml
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run quarantined tests as an advisory signal
|
||||
working-directory: src
|
||||
run: ./gradlew quarantineTest --no-daemon
|
||||
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()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
checks: read
|
||||
steps:
|
||||
- name: Require every current blocking job to succeed
|
||||
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: |
|
||||
@@ -194,7 +143,6 @@ jobs:
|
||||
for result in \
|
||||
"${QUALITY_RESULT}" \
|
||||
"${SAMPLE_OFF_RESULT}" \
|
||||
"${MATRIX_RESULT}" \
|
||||
"${REDIS_RESULT}" \
|
||||
"${JPA_CANDIDATE_RESULT}"; do
|
||||
if [[ "${result}" != "success" ]]; then
|
||||
@@ -203,3 +151,50 @@ jobs:
|
||||
fi
|
||||
done
|
||||
echo "release-gate: all current blocking quality jobs succeeded."
|
||||
|
||||
# `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.
|
||||
#
|
||||
# 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 }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
COMMIT_SHA: ${{ github.sha }}
|
||||
REQUIRED_CHECKS: trivy-fs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
deadline=$(( $(date +%s) + 1800 ))
|
||||
for required in ${REQUIRED_CHECKS}; do
|
||||
while :; do
|
||||
result="$(
|
||||
gh api \
|
||||
"repos/${GH_REPO}/commits/${COMMIT_SHA}/check-runs?check_name=${required}&per_page=100" \
|
||||
--jq '[.check_runs[]] | sort_by(.started_at) | last
|
||||
| if . == null then "absent none"
|
||||
else "\(.status) \(.conclusion // "none")" end'
|
||||
)"
|
||||
check_status="${result%% *}"
|
||||
check_conclusion="${result##* }"
|
||||
if [[ "${check_status}" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
if (( $(date +%s) >= deadline )); then
|
||||
echo "::error::release-gate: release-blocking check ${required} reported no result for ${COMMIT_SHA} (status ${check_status})"
|
||||
exit 1
|
||||
fi
|
||||
echo "release-gate: waiting for ${required} (status ${check_status})"
|
||||
sleep 30
|
||||
done
|
||||
if [[ "${check_conclusion}" != "success" ]]; then
|
||||
echo "::error::release-gate: release-blocking check ${required} concluded ${check_conclusion}"
|
||||
exit 1
|
||||
fi
|
||||
echo "release-gate: ${required} concluded success."
|
||||
done
|
||||
|
||||
@@ -35,18 +35,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Submit the resolved Gradle dependency graph
|
||||
uses: gradle/actions/dependency-submission@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # gradle/actions@v4.4.4
|
||||
with:
|
||||
@@ -179,7 +168,10 @@ jobs:
|
||||
trivy-kev.json | sort -u > found-cves.txt
|
||||
jq -r '.vulnerabilities[]?.cveID | select(type == "string")' \
|
||||
kev.json | sort -u > kev-cves.txt
|
||||
hits="$(comm -12 found-cves.txt kev-cves.txt || true)"
|
||||
# No `|| true`. comm exits non-zero only when it cannot read or order its inputs, and
|
||||
# swallowing that would have turned an unreadable CVE list into an empty intersection and
|
||||
# printed "no catalog match" — a KEV cross-check that passes because it never ran.
|
||||
hits="$(comm -12 found-cves.txt kev-cves.txt)"
|
||||
if [[ -n "${hits}" ]]; then
|
||||
echo "::error::CISA KEV-listed vulnerability found regardless of CVSS:"
|
||||
printf '%s\n' "${hits}"
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
name: fileserver-certification
|
||||
|
||||
# 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
|
||||
# checked, and a release tag reached none of them unless somebody remembered to press a button.
|
||||
#
|
||||
# `v*` is the only release tag. The adapter-scoped `fileserver-v*` pattern is gone: this repository
|
||||
# has one deployable unit (app-bootstrap), so an adapter-scoped tag could only ever run a subset of
|
||||
# the release gates and call the result a release — the tag-namespace split that release.yml exists
|
||||
# to end.
|
||||
#
|
||||
# These four jobs stay in their own file, and not in release.yml, for one mechanical reason:
|
||||
# FileserverDocumentationCoverageTest reads job ids out of `.github/workflows/fileserver-*.yml` and
|
||||
# requires every `fileserver-...` job docs/fileserver/support-matrix.md names to be defined in one
|
||||
# of them. Renaming the file or moving these jobs needs that document changed in the same change.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
fileserver-full-verification:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the architecture-wide dependency and module verification
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
verifyCleanArchitectureDependencies
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Run the complete fileserver suite across every leaf
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:application-core:check
|
||||
:adapter:inbound:web:check
|
||||
:adapter:outbound:fileserver:check
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
fileserver-documentation-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Prove every support claim maps to a job and every endpoint is documented
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:app-bootstrap:test --tests '*FileserverDocumentationCoverageTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
fileserver-pvc-certification:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
# This job checks the manifest, and only the manifest. It deliberately does not apply anything
|
||||
# to a cluster.
|
||||
#
|
||||
# There used to be a second step here that applied the job to a release cluster when
|
||||
# secrets.FILESERVER_PVC_KUBECONFIG was set and `exit 0`-ed with a ::warning:: when it was
|
||||
# not. With no secret configured — which is every fork of this template and was this
|
||||
# repository — the step printed a warning and the job went green under the name
|
||||
# "fileserver-pvc-certification", so a release read as ReadWriteOnce-certified against a
|
||||
# cluster nothing had ever touched. It also wrote a `certified` output that no job, step or
|
||||
# script in this repository read.
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
manifest=infra/fileserver/kubernetes/pvc-certification-job.yaml
|
||||
test -f "$manifest"
|
||||
grep -q 'kind: PersistentVolumeClaim' "$manifest"
|
||||
grep -q 'kind: Job' "$manifest"
|
||||
# ReadWriteMany is explicitly not claimed; a manifest that quietly widened the access
|
||||
# mode would certify a topology the support matrix says is uncertified.
|
||||
grep -q 'ReadWriteOnce' "$manifest"
|
||||
! grep -q 'ReadWriteMany' "$manifest"
|
||||
|
||||
fileserver-sensitive-telemetry-scan:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Prove telemetry carries no filename, path, or raw identifier
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:application-core:test --tests '*FileserverObservabilityTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -24,18 +24,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Start the NFSv4 certification environment
|
||||
run: docker compose -f infra/fileserver/nfs/compose.yml up -d --wait
|
||||
- name: Run the network-filesystem ambiguity suite
|
||||
@@ -54,18 +43,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the crash matrix and reconciliation suites
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -81,18 +59,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the large-file and slow-client suites under a constrained heap
|
||||
working-directory: src
|
||||
env:
|
||||
@@ -110,18 +77,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Prove no run commits bytes from a stale lease
|
||||
working-directory: src
|
||||
run: >-
|
||||
|
||||
@@ -23,9 +23,13 @@ on:
|
||||
- 'docs/registries/env-keys.yaml'
|
||||
- 'src/Dockerfile'
|
||||
- 'docker-compose.yml'
|
||||
- 'infra/nginx/**'
|
||||
- 'infra/k8s/**'
|
||||
- 'infra/fileserver/nginx/**'
|
||||
- 'infra/fileserver/kubernetes/**'
|
||||
- 'infra/fileserver/nfs/**'
|
||||
- '.github/workflows/fileserver-pr.yml'
|
||||
# Every Gradle job here installs its toolchain through this composite action, so a change to
|
||||
# it changes what this gate runs.
|
||||
- '.github/actions/setup-gradle-java/action.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -40,24 +44,13 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the fileserver application and architecture suites
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:application-core:test
|
||||
:app-bootstrap:test --tests '*CleanArchitectureTest' --tests '*Fileserver*'
|
||||
:app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*' --tests '*Fileserver*'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
@@ -66,18 +59,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Certify the local content store against the shared contract
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -91,18 +73,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the servlet and reactive transport contracts
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -116,18 +87,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the path, filename, range, and problem-detail hardening suite
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -142,18 +102,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Prove transfer cost does not scale with file size
|
||||
working-directory: src
|
||||
run: >-
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
name: fileserver-release
|
||||
|
||||
# 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.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
fileserver-full-verification:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the architecture-wide dependency and module verification
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
verifyCleanArchitectureDependencies
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Run the complete fileserver suite across every leaf
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:application-core:check
|
||||
:adapter:inbound:web:check
|
||||
:adapter:outbound:fileserver:check
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
fileserver-documentation-gate:
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Prove every support claim maps to a job and every endpoint is documented
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:app-bootstrap:test --tests '*FileserverDocumentationCoverageTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
fileserver-pvc-certification:
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
# Two different things, kept apart on purpose. The manifest checks below run everywhere and
|
||||
# fail on real drift; the cluster run needs a cluster and is skipped without one. The job
|
||||
# used to `test -f` the manifest and report success, which read as "ReadWriteOnce certified"
|
||||
# when nothing had been applied anywhere.
|
||||
- name: Check the certification manifest still says what the claim depends on
|
||||
run: |
|
||||
set -euo pipefail
|
||||
manifest=infra/fileserver/kubernetes/pvc-certification-job.yaml
|
||||
test -f "$manifest"
|
||||
grep -q 'kind: PersistentVolumeClaim' "$manifest"
|
||||
grep -q 'kind: Job' "$manifest"
|
||||
# ReadWriteMany is explicitly not claimed; a manifest that quietly widened the access
|
||||
# mode would certify a topology the support matrix says is uncertified.
|
||||
grep -q 'ReadWriteOnce' "$manifest"
|
||||
! grep -q 'ReadWriteMany' "$manifest"
|
||||
- name: Certify the ReadWriteOnce claim on the release cluster
|
||||
id: pvc-cluster-run
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.FILESERVER_PVC_KUBECONFIG }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${KUBECONFIG_CONTENT:-}" ]; then
|
||||
echo "::warning::no release cluster configured; PVC certification was NOT run."
|
||||
echo "The support matrix records this profile as Limited for exactly this reason:"
|
||||
echo "the cluster result is produced by an operator against a real cluster and read"
|
||||
echo "from docs/fileserver/storage-certification.md, not by this job."
|
||||
echo "certified=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
printf '%s' "$KUBECONFIG_CONTENT" > /tmp/kubeconfig
|
||||
export KUBECONFIG=/tmp/kubeconfig
|
||||
kubectl apply -f infra/fileserver/kubernetes/pvc-certification-job.yaml
|
||||
kubectl wait --for=condition=complete --timeout=30m job/fileserver-pvc-certification
|
||||
kubectl logs job/fileserver-pvc-certification
|
||||
echo "certified=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
fileserver-sensitive-telemetry-scan:
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Prove telemetry carries no filename, path, or raw identifier
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:application-core:test --tests '*FileserverObservabilityTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -1,132 +0,0 @@
|
||||
name: httpclient-contract
|
||||
|
||||
# Per-PR gate for the HTTP Client Platform (design §29). Each transport runs the same semantic
|
||||
# contract in its own job, so a transport that stops satisfying it fails on its own row instead of
|
||||
# disappearing into an aggregate run.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/adapter/outbound/httpclient/**'
|
||||
- 'src/app-bootstrap/src/**/httpclient/**'
|
||||
- 'docs/httpclient/**'
|
||||
- 'scripts/verify-httpclient-docs.py'
|
||||
- '.github/workflows/httpclient-contract.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
httpclient-unit-and-boundaries:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the focused module suite and the architecture gate
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:test
|
||||
verifyCleanArchitectureDependencies
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-stable-contract:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Certify one transport against the shared contract
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientStableContractTest
|
||||
-Phttpclient.contract.transports=${{ matrix.transport }}
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-security-and-compatibility:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the SSRF, cardinality, and Spring compatibility lanes
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientSecurityTest
|
||||
:adapter:outbound:httpclient:httpClientBlockHoundTest
|
||||
:adapter:outbound:httpclient:spring62ApiSurfaceScan
|
||||
:adapter:outbound:httpclient:spring70CompatibilityTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-composition:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Verify composition and architecture in the bootstrap module
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:app-bootstrap:test --tests '*httpclient*' --tests '*CleanArchitectureTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -1,94 +0,0 @@
|
||||
name: httpclient-nightly
|
||||
|
||||
# Lanes that need a container runtime, real time, or a QUIC-capable host (design §29). They are
|
||||
# separated from the per-PR gate rather than made optional inside it: a lane that cannot run here
|
||||
# fails, it does not skip.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
httpclient-fault-injection:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Inject TCP faults against a real upstream
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientFailureInjectionTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-performance:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Certify pool, streaming, retry, and rotation bounds
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientPerformanceTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-http3-experimental:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
# Experimental by design (D-08): the result is reported, never used to block a merge.
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Exercise the experimental HTTP/3 opt-in
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:test
|
||||
-Phttp3.tests.enabled=true
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -1,70 +0,0 @@
|
||||
name: httpclient-release
|
||||
|
||||
# Release gate for the HTTP Client Platform (design §38 step 4). 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.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src
|
||||
env:
|
||||
# A project property rather than a command-line flag, so each run command stays a plain,
|
||||
# verifiable task invocation while the machine-dependent bounds are still asserted.
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Focused module tests
|
||||
run: ./gradlew :adapter:outbound:httpclient:test --no-daemon --stacktrace
|
||||
- name: Spring 6.2 API surface lane
|
||||
run: ./gradlew :adapter:outbound:httpclient:spring62ApiSurfaceScan --no-daemon --stacktrace
|
||||
- name: Spring 7.0 compatibility lane
|
||||
run: ./gradlew :adapter:outbound:httpclient:spring70CompatibilityTest --no-daemon --stacktrace
|
||||
- name: Stable cross-transport contract suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientStableContractTest --no-daemon --stacktrace
|
||||
- name: SSRF and cardinality suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientSecurityTest --no-daemon --stacktrace
|
||||
- name: Event-loop blocking suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientBlockHoundTest --no-daemon --stacktrace
|
||||
- name: Toxiproxy fault-injection suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientFailureInjectionTest --no-daemon --stacktrace
|
||||
- name: Resource-bound performance certification
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientPerformanceTest --no-daemon --stacktrace
|
||||
- name: Architecture dependency gate
|
||||
run: ./gradlew verifyCleanArchitectureDependencies --no-daemon --stacktrace
|
||||
|
||||
httpclient-documentation:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Verify documentation matches the code
|
||||
run: python3 scripts/verify-httpclient-docs.py
|
||||
@@ -0,0 +1,236 @@
|
||||
name: integration-main
|
||||
|
||||
# Stage 2: is the merged state healthy.
|
||||
#
|
||||
# The question this stage answers is different from stage 1's. Stage 1 asks whether a diff is safe
|
||||
# and blocks a merge; stage 2 asks whether main is healthy and does not — the merge has already
|
||||
# happened. That difference is the point, and it is what lets a control exist without being an
|
||||
# obstacle: a gate here still fails loudly, it just fails after the thing it is reporting on.
|
||||
#
|
||||
# Two kinds of work live here.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
# are here rather than in stage 1 because every one of them either starts containers or re-runs
|
||||
# suites the PR gate already covers, and the pull-request budget is minutes for the whole gate.
|
||||
#
|
||||
# What is deliberately NOT here: the web and WebSocket "Advanced capability" nightly lanes that used
|
||||
# to exist as web-advanced-nightly.yml and websocket-advanced-nightly.yml. Both leaves' build files
|
||||
# say it outright — "They also run inside `test`, deliberately ... excluding them from the PR gate to
|
||||
# make this lane look meaningful would mean the PR gate stopped covering a fifth of the leaf" — so
|
||||
# `webAdvancedTest` and `websocketAdvancedTest` select tagged tests that `:<leaf>:test` already runs,
|
||||
# and `:<leaf>:test` runs inside the root `check` on every pull request and every push to main. The
|
||||
# strict lanes themselves survive in release.yml, where their fail-on-nothing-discovered guard is
|
||||
# worth a job.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
# 03:00 UTC. Late enough that the day's merges are in, early enough that a failure is triaged
|
||||
# before the next working day starts.
|
||||
- cron: '0 3 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
# 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:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the load, abuse and shutdown lanes on every container
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:test
|
||||
:adapter:inbound:web:webJettyCompatTest
|
||||
:adapter:inbound:web:webFluxContractTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-integration-reports
|
||||
path: src/adapter/inbound/web/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
|
||||
# Needs a container runtime and real time (design §29). Separated from the per-PR gate rather than
|
||||
# made optional inside it: a lane that cannot run here fails, it does not skip.
|
||||
httpclient-fault-injection:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Inject TCP faults against a real upstream
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientFailureInjectionTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-performance:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
# A project property rather than a command-line flag, so the run command stays a plain,
|
||||
# verifiable task invocation while the machine-dependent bounds are still asserted.
|
||||
GRADLE_OPTS: -Dorg.gradle.project.performance.assertions.enabled=true
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Certify pool, streaming, retry, and rotation bounds
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientPerformanceTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
httpclient-http3-experimental:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
# Experimental by design (D-08): the result is reported, never used to block a merge. Registered
|
||||
# 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
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Exercise the experimental HTTP/3 opt-in
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:test
|
||||
-Phttp3.tests.enabled=true
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# The six Docker-backed MongoDB lanes. Until now they ran in no workflow at all: the leaf excludes
|
||||
# every one of their tags from `test` (build.gradle "Docker-backed lanes are excluded from the
|
||||
# default unit run"), `check` gains only the hermetic `mongoStableContractTest`, and the only thing
|
||||
# that named them was scripts/verify-mongodb-platform.sh, which nothing in .github invokes. Six
|
||||
# lanes that fail closed without Docker, and no machine with Docker was ever asked to run them.
|
||||
#
|
||||
# Stage 2 rather than stage 1 because each lane starts real MongoDB containers — mongo:8.0.16,
|
||||
# mongo:7.0.28 and a Toxiproxy in front of a three-node replica set. That is minutes per lane, and
|
||||
# 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
|
||||
# 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:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
# Reuse would hand the failover lane a replica set another lane had already faulted.
|
||||
TESTCONTAINERS_REUSE_ENABLE: "false"
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Single-node replica set contract lane
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:persistence-mongo:mongoReplicaSetTest --no-daemon --stacktrace
|
||||
- name: Three-node failover lane
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:persistence-mongo:mongoFailoverTest --no-daemon --stacktrace
|
||||
- name: Migration and backfill restart lane
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:persistence-mongo:mongoMigrationTest --no-daemon --stacktrace
|
||||
- name: MongoDB 7.0 compatibility lane
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:persistence-mongo:mongoCompatibilityTest --no-daemon --stacktrace
|
||||
- name: Credential, TLS and redaction lane
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:persistence-mongo:mongoSecurityIntegrationTest --no-daemon --stacktrace
|
||||
- name: Resource-bound certification lane
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:persistence-mongo:mongoPerformanceTest --no-daemon --stacktrace
|
||||
- name: Publish the MongoDB lane reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: mongo-lane-reports
|
||||
path: src/adapter/outbound/persistence-mongo/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
|
||||
# The messaging contract evidence DAG. `verifyMessagingContracts` is the root of a chain that ran
|
||||
# nowhere: it depends on five qualification tasks (application-core, shared-contract,
|
||||
# sample-portfolio and two in adapter:outbound:messaging), each of which depends on
|
||||
# `prepareMessagingContractEvidence`; it is finalizedBy
|
||||
# `validateMessagingContractsEvidenceManifestSchema`; and it depends on
|
||||
# `validateMessagingJsonSchemaV1EvidenceManifestSchema`, which depends on
|
||||
# `verifyMessagingJsonSchemaV1`. Strict qualification tasks are registered outside `check` by
|
||||
# design (ca.strict-qualification.gradle), so none of the seven was reachable from any workflow.
|
||||
#
|
||||
# The schema validators are the part that matters. They re-read the manifest bytes the run just
|
||||
# wrote and validate them against config/messaging/evidence/build-evidence-manifest-v1.schema.json
|
||||
# — a manifest that claims a qualification nobody executed is exactly the failure they exist to
|
||||
# catch, and until now nothing executed them either.
|
||||
#
|
||||
# Stage 2 rather than stage 1: no containers, but it runs five qualification suites across four
|
||||
# leaves plus two JavaExec validators, and the tests it re-runs are already inside the PR gate's
|
||||
# `check`. What this job adds is the evidence manifest, which is a main-branch artifact.
|
||||
messaging-contract-evidence:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Qualify the messaging contract, catalog, binding and schema evidence
|
||||
working-directory: src
|
||||
run: ./gradlew verifyMessagingContracts --no-daemon --stacktrace
|
||||
- name: Publish the messaging evidence manifest
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: messaging-contract-evidence
|
||||
path: src/build/messaging-evidence/
|
||||
if-no-files-found: warn
|
||||
|
||||
# app-bootstrap's Testcontainers lane. The leaf gave it a source set of its own precisely so that
|
||||
# `./gradlew :app-bootstrap:test` would not require a Docker daemon — and the consequence nobody
|
||||
# closed is that a source set outside `test` is also outside `check`, so the real-PostgreSQL
|
||||
# outbox and idempotency contracts compiled on every build and executed on none.
|
||||
bootstrap-integration:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
TESTCONTAINERS_REUSE_ENABLE: "false"
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the real-PostgreSQL integration contracts
|
||||
working-directory: src
|
||||
run: ./gradlew :app-bootstrap:integrationTest --no-daemon --stacktrace
|
||||
@@ -19,22 +19,11 @@ jobs:
|
||||
hibernate8-compatibility:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Report Hibernate ORM 8 compatibility
|
||||
id: compatibility-probe
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
@@ -43,12 +32,15 @@ jobs:
|
||||
--stacktrace
|
||||
- name: Record what this lane did and did not execute
|
||||
if: always()
|
||||
env:
|
||||
PROBE_OUTCOME: ${{ steps.compatibility-probe.outcome }}
|
||||
run: |
|
||||
mkdir -p compatibility-evidence
|
||||
{
|
||||
echo "target=Hibernate 8"
|
||||
echo "target-coordinate=org.hibernate.orm:hibernate-core:8.x"
|
||||
echo "status=NOT_EXECUTABLE"
|
||||
echo "probe-result=${PROBE_OUTCOME}"
|
||||
echo "reason=Hibernate 8 is not resolvable from this build, so nothing has been compiled or run against it"
|
||||
echo "what-ran=the current runtime's own policy and lane-definition tests"
|
||||
echo "sha=${{ github.sha }}"
|
||||
|
||||
@@ -19,22 +19,11 @@ jobs:
|
||||
jpa4-compatibility:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Report Jakarta Persistence 4.0 compatibility
|
||||
id: compatibility-probe
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
@@ -43,12 +32,15 @@ jobs:
|
||||
--stacktrace
|
||||
- name: Record what this lane did and did not execute
|
||||
if: always()
|
||||
env:
|
||||
PROBE_OUTCOME: ${{ steps.compatibility-probe.outcome }}
|
||||
run: |
|
||||
mkdir -p compatibility-evidence
|
||||
{
|
||||
echo "target=Jakarta Persistence 4"
|
||||
echo "target-coordinate=jakarta.persistence:jakarta.persistence-api:4.x"
|
||||
echo "status=NOT_EXECUTABLE"
|
||||
echo "probe-result=${PROBE_OUTCOME}"
|
||||
echo "reason=the JPA 4 API is not on any configuration this build resolves, so nothing has been compiled against it"
|
||||
echo "what-ran=the current runtime's own policy and lane-definition tests"
|
||||
echo "sha=${{ github.sha }}"
|
||||
|
||||
@@ -25,22 +25,11 @@ jobs:
|
||||
postgresql19-compatibility:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Report PostgreSQL 19 compatibility
|
||||
id: compatibility-probe
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
@@ -49,12 +38,15 @@ jobs:
|
||||
--stacktrace
|
||||
- name: Record what this lane did and did not execute
|
||||
if: always()
|
||||
env:
|
||||
PROBE_OUTCOME: ${{ steps.compatibility-probe.outcome }}
|
||||
run: |
|
||||
mkdir -p compatibility-evidence
|
||||
{
|
||||
echo "target=PostgreSQL 19"
|
||||
echo "target-coordinate=postgres:19-alpine"
|
||||
echo "status=NOT_EXECUTABLE"
|
||||
echo "probe-result=${PROBE_OUTCOME}"
|
||||
echo "reason=no PostgreSQL 19 image is published yet, so no container of that major has ever been started by this lane"
|
||||
echo "what-ran=the current runtime's own policy and lane-definition tests"
|
||||
echo "sha=${{ github.sha }}"
|
||||
|
||||
@@ -30,18 +30,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Certify the platform against PostgreSQL ${{ matrix.postgresql }}
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -56,18 +45,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Reproduce deadlock, serialization, and commit-ambiguity scenarios
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -81,18 +59,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the query plan and database security suites
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -107,18 +74,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Verify pool saturation and REQUIRES_NEW connection behaviour
|
||||
working-directory: src
|
||||
# A behaviour contract, not a measurement. This step used to switch assertions off with an
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
name: jpa-pr
|
||||
|
||||
# Every "Stable" row in docs/jpa/support-matrix.md is backed by a job here or in jpa-nightly /
|
||||
# jpa-release. A support level with no job behind it is a marketing claim.
|
||||
#
|
||||
# The PR lane runs the oldest and the newest Stable PostgreSQL rather than all three: a behaviour
|
||||
# that differs across the matrix almost always differs at its ends, and the middle version is
|
||||
# covered nightly. What it does not do is skip the container lane on a runner without Docker —
|
||||
# PostgreSqlContainerFactory throws, because a skipped contract reports success for a database
|
||||
# nobody tested.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/adapter/outbound/persistence-jpa/**'
|
||||
- 'src/app-bootstrap/src/**/jpa/**'
|
||||
- 'src/config/architecture/modules.json'
|
||||
- 'docs/jpa/**'
|
||||
- 'docs/adr/ADR-JPA-*'
|
||||
- 'infra/jpa/**'
|
||||
- '.github/workflows/jpa-pr.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
jpa-unit-and-architecture:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the JPA unit and architecture suites
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:persistence-jpa:test
|
||||
:app-bootstrap:test --tests '*CleanArchitectureTest'
|
||||
verifyCleanArchitectureDependencies
|
||||
verifyOneTypePerFile
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
jpa-postgresql-contract:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# 16 and 18 — the ends of the Stable matrix. 17 runs nightly.
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Certify the platform against PostgreSQL ${{ matrix.postgresql }}
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:persistence-jpa:jpaPlatformContractTest
|
||||
-Pjpa.matrix.versions=${{ matrix.postgresql }}
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
jpa-migration-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the migration upgrade smoke scenarios
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:persistence-jpa:jpaPlatformMigrationTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -26,18 +26,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Verify the production-profile JPA R2 manifest DAG
|
||||
working-directory: src
|
||||
run: >-
|
||||
|
||||
@@ -43,18 +43,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the full JPA release gate on PostgreSQL ${{ matrix.postgresql }}
|
||||
working-directory: src
|
||||
run: >-
|
||||
@@ -126,25 +115,14 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Verify architecture boundaries and the support matrix
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
verifyCleanArchitectureDependencies
|
||||
verifyOneTypePerFile
|
||||
:app-bootstrap:test --tests '*CleanArchitectureTest'
|
||||
checkstyleMain
|
||||
:app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*'
|
||||
:adapter:outbound:persistence-jpa:test --tests '*JpaReleaseManifestTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
@@ -17,6 +17,9 @@ on:
|
||||
paths:
|
||||
- "src/messaging/**"
|
||||
- ".github/workflows/messaging-certification.yml"
|
||||
# Every Gradle job here installs its toolchain through this composite action, so a change to
|
||||
# it changes what this gate runs.
|
||||
- ".github/actions/setup-gradle-java/action.yml"
|
||||
schedule:
|
||||
- cron: "41 4 * * 3"
|
||||
workflow_dispatch:
|
||||
@@ -33,18 +36,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Certify the Kafka adapter against a real broker
|
||||
working-directory: src
|
||||
# GITHUB_SHA is read by the lane and written into every evidence line, because "certified"
|
||||
|
||||
@@ -31,6 +31,9 @@ on:
|
||||
- 'docs/notification/**'
|
||||
- 'infra/notification/**'
|
||||
- '.github/workflows/notification-platform.yml'
|
||||
# Every Gradle job here installs its toolchain through this composite action, so a change to
|
||||
# it changes what this gate runs.
|
||||
- '.github/actions/setup-gradle-java/action.yml'
|
||||
push:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
@@ -52,18 +55,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Compile and format check
|
||||
working-directory: src
|
||||
run: ./gradlew :application-core:compileJava :adapter:outbound:notification:compileJava --console=plain
|
||||
@@ -90,11 +82,11 @@ jobs:
|
||||
working-directory: src
|
||||
run: |
|
||||
./gradlew verifyCleanArchitectureDependencies --console=plain
|
||||
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --tests '*NotificationArchitectureTest' --console=plain
|
||||
./gradlew :app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*' --console=plain
|
||||
- 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
|
||||
@@ -113,18 +105,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- 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 —
|
||||
# so the job name was the only place those three properties existed.
|
||||
@@ -153,50 +134,14 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
provider-sandbox:
|
||||
name: provider sandbox smoke (secret-protected, non-blocking)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
environment: notification-provider-sandbox
|
||||
# Not a required check: an external outage must not block a merge. But not continue-on-error
|
||||
# either — a job that cannot fail produces no evidence, and this job's entire previous body was
|
||||
# two echo statements, which is what let five channels be graded Stable on nothing.
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Refuse to report a pass with no credentials
|
||||
env:
|
||||
NOTIFICATION_SANDBOX_CREDENTIALS: ${{ secrets.NOTIFICATION_SANDBOX_CREDENTIALS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${NOTIFICATION_SANDBOX_CREDENTIALS:-}" ]; then
|
||||
echo "provider sandbox credentials are not configured for this environment." >&2
|
||||
echo "The job stops here rather than reporting a green run that called nothing." >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Smoke test against real provider sandboxes
|
||||
working-directory: src
|
||||
env:
|
||||
NOTIFICATION_SANDBOX_ENABLED: 'true'
|
||||
NOTIFICATION_SANDBOX_CREDENTIALS: ${{ secrets.NOTIFICATION_SANDBOX_CREDENTIALS }}
|
||||
run: ./gradlew :adapter:outbound:notification:test --tests '*ProviderSandbox*' --console=plain
|
||||
- name: Upload the wire evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: notification-provider-sandbox-evidence
|
||||
path: src/adapter/outbound/notification/build/test-results/test/
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
# There is no provider-sandbox job. It ran only on workflow_dispatch and could not succeed by
|
||||
# any path: with no credentials its first step exit 1-ed, and with credentials the only test it
|
||||
# ran was ProviderSandboxSmokeTest, whose body is an unconditional fail() saying a real sandbox
|
||||
# call is not implemented. Its credential check read secrets.NOTIFICATION_SANDBOX_CREDENTIALS,
|
||||
# which nothing in this repository consumes — the test reads NOTIFICATION_SANDBOX_ENABLED — so
|
||||
# any non-empty string satisfied it and was then dropped.
|
||||
#
|
||||
# The unimplemented state is still stated in two places that do not depend on a workflow:
|
||||
# ProviderSandboxSmokeTest itself, and the unsatisfied provider-wire-qualified claim in
|
||||
# docs/notification/evidence-manifest.json, which verifyNotificationEvidence enforces inside
|
||||
# check. When a real sandbox call is implemented, the job comes back with it.
|
||||
|
||||
@@ -11,6 +11,17 @@ on:
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
# AwsS3DirectTransferQualificationTest requires a second, separate authority
|
||||
# (OBJECT_STORAGE_AWS_DIRECT_MUTATION_ENABLED) before the direct-transfer lane may run, and
|
||||
# the job never supplied it. objectStorageAwsQualificationTest is a strict qualification task
|
||||
# that requires both of its classes, so the lane could not be run to a pass from any input:
|
||||
# dispatching it always failed on the missing variable. The authority now exists as its own
|
||||
# input rather than as a constant, which is what "separate" was supposed to mean.
|
||||
run_protected_aws_direct_mutation:
|
||||
description: Also authorize the direct-transfer mutation lane against the sandbox bucket
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -23,18 +34,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run non-skipping Poster image migration qualification
|
||||
working-directory: src
|
||||
run: ./gradlew :sample-portfolio:posterImageMigrationTest --no-daemon --stacktrace
|
||||
@@ -43,18 +43,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run exact-release MinIO managed contract
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:objectstorage:objectStorageMinioContractTest --no-daemon --stacktrace
|
||||
@@ -64,45 +53,27 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run digest-pinned MinIO and Toxiproxy fault contract
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:objectstorage:objectStorageMinioFaultTest --no-daemon --stacktrace
|
||||
|
||||
aws-managed-common-subset:
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.run_protected_aws
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch'
|
||||
&& inputs.run_protected_aws
|
||||
&& inputs.run_protected_aws_direct_mutation
|
||||
environment: object-storage-aws-qualification
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OBJECT_STORAGE_AWS_QUALIFICATION_ENABLED: "true"
|
||||
OBJECT_STORAGE_AWS_DIRECT_MUTATION_ENABLED: ${{ inputs.run_protected_aws_direct_mutation }}
|
||||
OBJECT_STORAGE_AWS_BUCKET: ${{ secrets.OBJECT_STORAGE_AWS_BUCKET }}
|
||||
OBJECT_STORAGE_AWS_REGION: ${{ secrets.OBJECT_STORAGE_AWS_REGION }}
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run protected AWS common-subset qualification
|
||||
working-directory: src
|
||||
run: ./gradlew :adapter:outbound:objectstorage:objectStorageAwsQualificationTest --no-daemon --stacktrace
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
name: pr-adapters
|
||||
|
||||
# Stage 1, the adapter half: the lanes a pull request must clear that `ci-quality-gates.yml` cannot
|
||||
# reach.
|
||||
#
|
||||
# It replaces web-pr.yml, websocket-pr.yml, httpclient-contract.yml and jpa-pr.yml, which were four
|
||||
# files split by module rather than by stage. Splitting by module is what made the duplication
|
||||
# invisible: each file opened with its own "unit and architecture" job running
|
||||
# `:<leaf>:test verifyCleanArchitectureDependencies`, and all four of those were already inside the
|
||||
# root `check` that ci-quality-gates.yml runs on every pull request with no path filter. Four jobs,
|
||||
# four runners, four Gradle configurations, zero additional coverage. They are gone; what is left
|
||||
# here is only what `check` does not run.
|
||||
#
|
||||
# What `check` does not run, and therefore what this file is for:
|
||||
# * lanes with their own source set — a second servlet container, a real Nginx, Reactor Netty;
|
||||
# * lanes selected by a tag that `test` excludes — the cross-stack parity recording comparison;
|
||||
# * lanes parameterised per run — one PostgreSQL major per job, one HTTP transport per job.
|
||||
# Each of those genuinely cannot run inside `check`, which is the test for whether a job belongs
|
||||
# here at all.
|
||||
#
|
||||
# Path filtering is per job rather than per workflow. The four files it replaces each carried an
|
||||
# `on.pull_request.paths` list, so the whole file was skipped or run as a unit; a change touching
|
||||
# web and JPA started two workflows and a change touching neither still started none. Here one
|
||||
# `changes` job computes the answer once from the pull request's own diff and every lane reads it.
|
||||
# The filter is a plain `git diff` rather than a filter action: this repository pins every action by
|
||||
# commit SHA and adding a third-party action to compute a boolean is a supply-chain decision, not a
|
||||
# convenience.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# One diff, read once. `workflow_dispatch` answers "everything changed", because a manual run is
|
||||
# somebody asking for the lanes and there is no base ref to compare against.
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
web: ${{ steps.filter.outputs.web }}
|
||||
websocket: ${{ steps.filter.outputs.websocket }}
|
||||
httpclient: ${{ steps.filter.outputs.httpclient }}
|
||||
jpa: ${{ steps.filter.outputs.jpa }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
with:
|
||||
# Both endpoints of the pull request's diff have to be present locally; the default
|
||||
# shallow fetch has neither the base commit nor the merge base.
|
||||
fetch-depth: 0
|
||||
- name: Decide which adapter lanes this diff can affect
|
||||
id: filter
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "${GITHUB_EVENT_NAME}" != 'pull_request' ]; then
|
||||
changed='ALL'
|
||||
else
|
||||
if [ -z "${BASE_SHA}" ] || [ -z "${HEAD_SHA}" ]; then
|
||||
echo "::error::pull request diff endpoints are missing; refusing to report no lanes"
|
||||
exit 1
|
||||
fi
|
||||
changed="$(git diff --name-only "${BASE_SHA}" "${HEAD_SHA}")"
|
||||
fi
|
||||
# Fail closed rather than reporting "nothing changed": an empty diff on a pull request
|
||||
# means the comparison did not work, and a filter that answers false on a broken
|
||||
# comparison silently turns every lane below off.
|
||||
if [ "${changed}" != 'ALL' ] && [ -z "${changed}" ]; then
|
||||
echo "::error::the pull request diff is empty; the comparison did not run"
|
||||
exit 1
|
||||
fi
|
||||
printf 'changed files:\n%s\n' "${changed}"
|
||||
emit() {
|
||||
lane="$1"
|
||||
shift
|
||||
if [ "${changed}" = 'ALL' ]; then
|
||||
printf '%s=true\n' "${lane}" >> "${GITHUB_OUTPUT}"
|
||||
printf 'lane %s: true (manual run)\n' "${lane}"
|
||||
return 0
|
||||
fi
|
||||
for pattern in "$@"; do
|
||||
if printf '%s\n' "${changed}" | grep -qE -- "${pattern}"; then
|
||||
printf '%s=true\n' "${lane}" >> "${GITHUB_OUTPUT}"
|
||||
printf 'lane %s: true (%s)\n' "${lane}" "${pattern}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
printf '%s=false\n' "${lane}" >> "${GITHUB_OUTPUT}"
|
||||
printf 'lane %s: false\n' "${lane}"
|
||||
}
|
||||
# This workflow and the composite action every lane below uses are in every lane's path
|
||||
# set: a change to either changes what the lanes do, and a gate that does not re-run when
|
||||
# its own definition changes is a gate nobody has seen run in its current form.
|
||||
common='^\.github/workflows/pr-adapters\.yml$|^\.github/actions/'
|
||||
emit web \
|
||||
'^src/adapter/inbound/web/' \
|
||||
'^src/application-core/src/.*/operation/' \
|
||||
'^src/application-core/src/.*/idempotency/' \
|
||||
'^src/adapter/outbound/persistence-jpa/src/.*/operation/' \
|
||||
'^docs/web/' \
|
||||
"${common}"
|
||||
emit websocket \
|
||||
'^src/adapter/inbound/websocket/' \
|
||||
'^docs/websocket/' \
|
||||
"${common}"
|
||||
emit httpclient \
|
||||
'^src/adapter/outbound/httpclient/' \
|
||||
'^src/app-bootstrap/src/.*/httpclient/' \
|
||||
'^docs/httpclient/' \
|
||||
'^scripts/verify-httpclient-docs\.py$' \
|
||||
"${common}"
|
||||
emit jpa \
|
||||
'^src/adapter/outbound/persistence-jpa/' \
|
||||
'^src/app-bootstrap/src/.*/jpa/' \
|
||||
'^src/config/architecture/modules\.json$' \
|
||||
'^docs/jpa/' \
|
||||
'^docs/adr/ADR-JPA-' \
|
||||
'^infra/jpa/' \
|
||||
"${common}"
|
||||
|
||||
# The parity gate depends on all three recording lanes and fails when one is missing, so it runs
|
||||
# them itself rather than trusting a previous job to have left the recordings behind. Its tag is
|
||||
# excluded from `test`, which is why `check` cannot cover it.
|
||||
web-cross-stack-parity:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.web == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Compare the wire contract across Tomcat, Jetty and Reactor Netty
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webCrossStackParityTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the parity recordings
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-contract-parity
|
||||
path: src/adapter/inbound/web/build/web-contract-parity/
|
||||
if-no-files-found: error
|
||||
|
||||
# Docker-gated, and the lane fails rather than skipping when the runtime is missing. A proxy
|
||||
# contract that quietly passes without a proxy has been certifying nothing since whenever the
|
||||
# container runtime last broke.
|
||||
web-nginx-proxy-contract:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.web == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the proxy, prefix and spoofing contract behind a real Nginx
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webNginxProxyTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
websocket-container-matrix:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.websocket == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the runtime contract on the second servlet container
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:websocketJettyTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# Docker-gated, and the lane fails rather than skipping. Upgrade handling is the single most
|
||||
# common WebSocket deployment failure and it is invisible from either side alone.
|
||||
websocket-nginx-contract:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.websocket == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the upgrade and forwarded-header contract behind a real Nginx
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:websocketNginxTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# One transport per job, so a transport that stops satisfying the shared contract fails on its own
|
||||
# row instead of disappearing into an aggregate run. `check` runs this lane once, unparameterised.
|
||||
httpclient-stable-contract:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.httpclient == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
transport: [apache, jdk, reactor]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Certify one transport against the shared contract
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:httpClientStableContractTest
|
||||
-Phttpclient.contract.transports=${{ matrix.transport }}
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# Only the Spring 7.0 lane. httpClientSecurityTest, httpClientBlockHoundTest and
|
||||
# spring62ApiSurfaceScan used to run here too; all three are `dependsOn` of this leaf's `check`
|
||||
# (src/adapter/outbound/httpclient/build.gradle), so ci-quality-gates.yml already ran them on the
|
||||
# same pull request. spring70CompatibilityTest is deliberately outside `check` and is what is left.
|
||||
httpclient-security-and-compatibility:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.httpclient == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the next-major Spring compatibility lane
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:httpclient:spring70CompatibilityTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# 16 and 18 — the ends of the Stable matrix. 17 runs in the integration stage. What this does not
|
||||
# do is skip the container lane on a runner without Docker: PostgreSqlContainerFactory throws,
|
||||
# because a skipped contract reports success for a database nobody tested.
|
||||
jpa-postgresql-contract:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.jpa == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
postgresql: ["16", "18"]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Certify the platform against PostgreSQL ${{ matrix.postgresql }}
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:persistence-jpa:jpaPlatformContractTest
|
||||
-Pjpa.matrix.versions=${{ matrix.postgresql }}
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
jpa-migration-smoke:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.jpa == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the migration upgrade smoke scenarios
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:persistence-jpa:jpaPlatformMigrationTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -34,6 +34,9 @@ on:
|
||||
- "src/adapter/outbound/cache-redis/**"
|
||||
- "infra/redis-sdk/**"
|
||||
- ".github/workflows/redis-sdk-topology.yml"
|
||||
# Every Gradle job here installs its toolchain through this composite action, so a change to
|
||||
# it changes what this gate runs.
|
||||
- ".github/actions/setup-gradle-java/action.yml"
|
||||
schedule:
|
||||
# 02:30 UTC daily. Nightly, not hourly: the matrix starts real servers.
|
||||
- cron: "30 2 * * *"
|
||||
@@ -113,18 +116,7 @@ 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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Start the topology
|
||||
env:
|
||||
REDIS_VERSION: ${{ matrix.redis_version }}
|
||||
@@ -135,8 +127,19 @@ jobs:
|
||||
set -euo pipefail
|
||||
# The tag says 7.4; the digest says which 7.4. Evidence that names only the tag cannot be
|
||||
# reproduced once the tag moves.
|
||||
#
|
||||
# This used to end in `|| echo 'unresolved'`, which absorbed the failure that `set -e` was
|
||||
# there to catch: the manifest below recorded `image_digest=unresolved`, the upload
|
||||
# satisfied `if-no-files-found: error`, and the lane went green holding exactly the
|
||||
# artifact this workflow's header calls "not evidence". Compose pulls the image in the
|
||||
# step before this one, so RepoDigests is populated; if it is not, the run has nothing to
|
||||
# certify and says so.
|
||||
digest="$(docker image inspect --format '{{index .RepoDigests 0}}' \
|
||||
"redis:${{ matrix.redis_version }}" 2>/dev/null || echo 'unresolved')"
|
||||
"redis:${{ matrix.redis_version }}")"
|
||||
if [[ -z "$digest" ]]; then
|
||||
echo "::error::no repository digest for redis:${{ matrix.redis_version }}; this run cannot say which image produced its evidence"
|
||||
exit 1
|
||||
fi
|
||||
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"
|
||||
- name: Run the topology contracts
|
||||
working-directory: src
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
name: release
|
||||
|
||||
# Stage 3: produce a deployable artifact.
|
||||
#
|
||||
# One workflow, because there is one deployable unit. `app-bootstrap` is the composition root and
|
||||
# the only thing a cluster runs; the adapters are leaves of that artifact, not independently
|
||||
# shippable services. Eight files used to answer a release tag — web-release, web-advanced-release,
|
||||
# websocket-release, httpclient-release, container-release, and the three that still have to live
|
||||
# apart (see below) — and between them they ran `verifyCleanArchitectureDependencies` six times and
|
||||
# `:app-bootstrap:test` four times for one release, on separate runners, with no job in any of them
|
||||
# able to wait on a job in another.
|
||||
#
|
||||
# Tag scheme: `v*` only. The adapter-scoped patterns (`web-v*`, `websocket-v*`, `fileserver-v*`) are
|
||||
# gone. They were the namespace-split bug: tagging `v1.2.3` and tagging `web-v1.2.3` ran different
|
||||
# sets of gates, so a release could choose which gate it cleared, and the adapter-scoped half could
|
||||
# not build an image because there is no per-adapter image to build.
|
||||
#
|
||||
# Two release workflows still stand outside this file, both for a mechanical reason rather than a
|
||||
# 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-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 — 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,
|
||||
# because `needs:` does not reach across workflows.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Never cancel a release in flight. A half-pushed manifest is worse than a slow one, and two runs
|
||||
# for the same tag would race for the same registry tags.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# The architecture-wide verification, once. Each of the four release workflows this file replaces
|
||||
# ran `verifyCleanArchitectureDependencies` on its own runner, and three of them also ran the
|
||||
# bootstrap architecture suite; the answers were identical because the input was one commit.
|
||||
architecture-and-surface:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Verify architecture boundaries and the published surfaces
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
verifyCleanArchitectureDependencies
|
||||
verifyPublicPathSnapshot
|
||||
:app-bootstrap:verifyEnvKeys
|
||||
:app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# Every web lane that `check` cannot reach. webCrossStackParityTest depends on `test`,
|
||||
# webJettyCompatTest and webFluxContractTest, so naming it runs all four — which is what
|
||||
# web-advanced-release.yml spent a separate 90-minute job doing by naming the three by hand.
|
||||
#
|
||||
# webAdvancedTest is here rather than in a nightly of its own. Its tests run inside
|
||||
# `:adapter:inbound:web:test` by design, so the lane adds exactly one thing: it fails closed when
|
||||
# the `web-advanced` tag selects nothing. That is worth asserting at a release and is not worth a
|
||||
# workflow file and a runner every night.
|
||||
web-stable-release-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run every web lane, Stable and Advanced
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webCrossStackParityTest
|
||||
:adapter:inbound:web:webNginxProxyTest
|
||||
:adapter:inbound:web:webAdvancedTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the release evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-release-evidence
|
||||
path: |
|
||||
src/adapter/inbound/web/build/web-contract-parity/
|
||||
src/adapter/inbound/web/build/reports/tests/
|
||||
if-no-files-found: error
|
||||
|
||||
websocket-stable-release-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run every websocket lane, Stable and Advanced
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:test
|
||||
:adapter:inbound:websocket:websocketJettyTest
|
||||
:adapter:inbound:websocket:websocketNginxTest
|
||||
:adapter:inbound:websocket:websocketTransportQualificationTest
|
||||
:adapter:inbound:websocket:websocketAdvancedTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the release evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: websocket-release-evidence
|
||||
path: src/adapter/inbound/websocket/build/reports/tests/
|
||||
if-no-files-found: error
|
||||
|
||||
# The three gRPC certification lanes. Their tests already run on every pull request — the
|
||||
# `grpc-inprocess`, `grpc-netty` and `grpc-fault` tags are NOT excluded from
|
||||
# `:grpc:grpc-testkit:test` (only `grpc-performance` is), and that task runs inside the root
|
||||
# `check`. So this job adds exactly what the web and WebSocket Advanced lanes above add: the lane
|
||||
# fails closed when its tag selects nothing, which is the one thing a tag-filtered suite inside
|
||||
# `test` cannot tell you. A renamed or deleted @Tag would otherwise leave the in-process,
|
||||
# transport and fault evidence grades claiming coverage that stopped existing.
|
||||
#
|
||||
# Release rather than nightly, for the same reason web-stable-release-gate is: these lanes need no
|
||||
# container and no fixed cadence — grpcNettyContractTest opens an ephemeral socket, not a broker —
|
||||
# so the guard is worth asserting once per tag and is not worth a runner every night.
|
||||
#
|
||||
# grpcPerformanceTest is deliberately absent. The leaf excludes it from `test` and says why: "a
|
||||
# measurement in the release gate is a flaky test on a shared CI runner; it runs when somebody asks
|
||||
# for it, by name." It is recorded as a manual entrypoint in the repository README instead.
|
||||
grpc-stable-release-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: In-process contract lane
|
||||
working-directory: src
|
||||
run: ./gradlew :grpc:grpc-testkit:grpcInProcessContractTest --no-daemon --stacktrace
|
||||
- name: Real Netty transport lane
|
||||
working-directory: src
|
||||
run: ./gradlew :grpc:grpc-testkit:grpcNettyContractTest --no-daemon --stacktrace
|
||||
- name: Fault-injection lane
|
||||
working-directory: src
|
||||
run: ./gradlew :grpc:grpc-testkit:grpcFaultTest --no-daemon --stacktrace
|
||||
- name: Publish the gRPC release evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: grpc-release-evidence
|
||||
path: src/grpc/grpc-testkit/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
|
||||
# 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
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src
|
||||
env:
|
||||
# A project property rather than a command-line flag, so each run command stays a plain,
|
||||
# verifiable task invocation while the machine-dependent bounds are still asserted.
|
||||
GRADLE_OPTS: -Dorg.gradle.project.performance.assertions.enabled=true
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Focused module tests
|
||||
run: ./gradlew :adapter:outbound:httpclient:test --no-daemon --stacktrace
|
||||
- name: Spring 6.2 API surface lane
|
||||
run: ./gradlew :adapter:outbound:httpclient:spring62ApiSurfaceScan --no-daemon --stacktrace
|
||||
- name: Spring 7.0 compatibility lane
|
||||
run: ./gradlew :adapter:outbound:httpclient:spring70CompatibilityTest --no-daemon --stacktrace
|
||||
- name: Stable cross-transport contract suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientStableContractTest --no-daemon --stacktrace
|
||||
- name: SSRF and cardinality suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientSecurityTest --no-daemon --stacktrace
|
||||
- name: Event-loop blocking suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientBlockHoundTest --no-daemon --stacktrace
|
||||
- name: Toxiproxy fault-injection suite
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientFailureInjectionTest --no-daemon --stacktrace
|
||||
- name: Resource-bound performance certification
|
||||
run: ./gradlew :adapter:outbound:httpclient:httpClientPerformanceTest --no-daemon --stacktrace
|
||||
|
||||
httpclient-documentation:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Verify documentation matches the code
|
||||
run: python3 scripts/verify-httpclient-docs.py
|
||||
|
||||
app-image-release:
|
||||
needs:
|
||||
- architecture-and-surface
|
||||
- web-stable-release-gate
|
||||
- websocket-stable-release-gate
|
||||
- grpc-stable-release-gate
|
||||
- httpclient-release-gate
|
||||
- httpclient-documentation
|
||||
# Job-level, because a job that declares `permissions:` replaces the workflow set entirely: this
|
||||
# is the only job that writes anything anywhere, and `packages: write` stops at its boundary.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
||||
# 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.
|
||||
# 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.
|
||||
#
|
||||
# GHCR rejects an uppercase path, and this repository's owner is mixed case — the naive
|
||||
# `ghcr.io/${{ github.repository }}` fails at push time with a message about the manifest
|
||||
# rather than about the case, so the lowercasing is explicit and the result is asserted.
|
||||
- name: Resolve the release coordinates
|
||||
env:
|
||||
CONFIGURED_IMAGE_NAME: ${{ vars.APP_IMAGE_NAME }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
readonly REGISTRY='ghcr.io'
|
||||
if [[ "${GITHUB_REF_TYPE}" != 'tag' ]]; then
|
||||
echo "::error::container-release runs only for a release tag; ref type was ${GITHUB_REF_TYPE}"
|
||||
exit 1
|
||||
fi
|
||||
release_tag="${GITHUB_REF_NAME}"
|
||||
# Bare MAJOR.MINOR.PATCH, because src/build.gradle's release-version guard refuses a
|
||||
# pre-release or build suffix and the image tag must be the same string the JAR reports.
|
||||
if [[ ! "${release_tag}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||
echo "::error::release tag must be vMAJOR.MINOR.PATCH with no suffix; got '${release_tag}'"
|
||||
exit 1
|
||||
fi
|
||||
release_version="${BASH_REMATCH[1]}"
|
||||
owner_path="$(printf '%s' "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')"
|
||||
image_name="${CONFIGURED_IMAGE_NAME:-${owner_path}/caskeleton}"
|
||||
image_repository="${REGISTRY}/${image_name}"
|
||||
if [[ "${image_repository}" != "${image_repository,,}" ]]; then
|
||||
echo "::error::image repository must be lowercase; got '${image_repository}'"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${image_repository}" =~ [[:space:]] || "${image_repository}" == *:* ]]; then
|
||||
echo "::error::image repository must carry no tag and no whitespace; got '${image_repository}'"
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
printf 'REGISTRY=%s\n' "${REGISTRY}"
|
||||
printf 'RELEASE_VERSION=%s\n' "${release_version}"
|
||||
printf 'BUILD_VERSION=%s+%s\n' "${release_version}" "${GITHUB_SHA}"
|
||||
printf 'IMAGE_REPOSITORY=%s\n' "${image_repository}"
|
||||
printf 'IMAGE_VERSION_TAG=%s\n' "${release_version}"
|
||||
printf 'IMAGE_REVISION_TAG=sha-%s\n' "${GITHUB_SHA}"
|
||||
printf 'SOURCE_URL=%s/%s\n' "${GITHUB_SERVER_URL}" "${GITHUB_REPOSITORY}"
|
||||
} >> "${GITHUB_ENV}"
|
||||
printf 'container-release: %s -> %s:%s and %s:sha-%s\n' \
|
||||
"${release_tag}" "${image_repository}" "${release_version}" \
|
||||
"${image_repository}" "${GITHUB_SHA}"
|
||||
# Byte-identical to the install in dependency-vulnerability.yml, deliberately: the same
|
||||
# checksum-pinned binary at the same version scans the filesystem and the image, so the two
|
||||
# gates cannot disagree because one of them silently moved to a newer database schema.
|
||||
#
|
||||
# This repository installs its scanner rather than calling a scanner action, which is why no
|
||||
# third-party action appears in this workflow: a pinned tarball with an asserted SHA-256 is a
|
||||
# supply-chain claim that can be checked offline, and an action pinned to a commit is not.
|
||||
- name: Install pinned Trivy under RUNNER_TEMP
|
||||
env:
|
||||
TRIVY_DOWNLOAD_BASE_URL: ${{ vars.TRIVY_DOWNLOAD_BASE_URL }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
readonly TRIVY_VERSION='0.71.2'
|
||||
readonly TRIVY_SHA256_AMD64='0510e71e2fd39bf863856d499c8dc19feb4e7336546394c502a8f5cc7ab27460'
|
||||
readonly TRIVY_SHA256_ARM64='fe1c7106e15a5365d485b098a8c338f91e3b7ba71cb0e4963b98a3a098763cfc'
|
||||
readonly DOWNLOAD_BASE_URL="${TRIVY_DOWNLOAD_BASE_URL:-https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}}"
|
||||
case "${RUNNER_ARCH:-X64}" in
|
||||
X64)
|
||||
asset_arch='64bit'
|
||||
expected_sha256="${TRIVY_SHA256_AMD64}"
|
||||
;;
|
||||
ARM64)
|
||||
asset_arch='ARM64'
|
||||
expected_sha256="${TRIVY_SHA256_ARM64}"
|
||||
;;
|
||||
*)
|
||||
echo "::error::unsupported runner architecture: ${RUNNER_ARCH:-unknown}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
install_dir="${RUNNER_TEMP}/trivy-${TRIVY_VERSION}"
|
||||
archive="${RUNNER_TEMP}/trivy-${TRIVY_VERSION}.tar.gz"
|
||||
mkdir -p "${install_dir}"
|
||||
curl --fail --show-error --silent --location --retry 3 \
|
||||
--proto '=https' --tlsv1.2 \
|
||||
"${DOWNLOAD_BASE_URL}/trivy_${TRIVY_VERSION}_Linux-${asset_arch}.tar.gz" \
|
||||
--output "${archive}"
|
||||
printf '%s %s\n' "${expected_sha256}" "${archive}" | sha256sum -c -
|
||||
tar -xzf "${archive}" -C "${install_dir}" trivy
|
||||
chmod 0755 "${install_dir}/trivy"
|
||||
printf '%s\n' "${install_dir}" >> "${GITHUB_PATH}"
|
||||
# SOURCE_DATE_EPOCH is the commit time, not the wall clock, so the image metadata is a function
|
||||
# of the commit rather than of when the runner happened to pick the job up. Verified locally,
|
||||
# and worth stating exactly because it is easy to overclaim: BuildKit uses it for the image
|
||||
# config `created` field and for every history timestamp — both came back as the commit time —
|
||||
# and it does NOT rewrite file mtimes inside the layers. Those still carry the build time, so
|
||||
# two builds of the same commit agree on metadata but their layer digests still differ.
|
||||
# Byte-identical layers additionally need `--output type=image,rewrite-timestamp=true`, which
|
||||
# needs the containerd image store; that is a runner-capability change, not a flag to add
|
||||
# untested to the one job that publishes releases.
|
||||
#
|
||||
# The OCI `created` label comes from the same commit for the same reason: `date -u` there would
|
||||
# have made every rebuild a different image for no reason anybody could see.
|
||||
#
|
||||
# Both base images are already digest-pinned inside src/Dockerfile, and so is the Dockerfile
|
||||
# frontend in its `# syntax` directive, so nothing in this build resolves a floating tag.
|
||||
- name: Build the release image
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
|
||||
export SOURCE_DATE_EPOCH
|
||||
created="$(git log -1 --format=%cI)"
|
||||
printf 'SOURCE_DATE_EPOCH=%s (%s)\n' "${SOURCE_DATE_EPOCH}" "${created}"
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--file src/Dockerfile \
|
||||
--tag "${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}" \
|
||||
--tag "${IMAGE_REPOSITORY}:${IMAGE_REVISION_TAG}" \
|
||||
--build-arg RELEASE_VERSION="${RELEASE_VERSION}" \
|
||||
--build-arg BUILD_VERSION="${BUILD_VERSION}" \
|
||||
--build-arg GIT_SHA="${GITHUB_SHA}" \
|
||||
--build-arg SOURCE_URL="${SOURCE_URL}" \
|
||||
--label org.opencontainers.image.created="${created}" \
|
||||
src
|
||||
docker image inspect \
|
||||
--format 'built {{.Id}} ({{.Size}} bytes, {{len .RootFS.Layers}} layers)' \
|
||||
"${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}"
|
||||
# Generated before the blocking scan, and uploaded before it too, so the inventory of what is
|
||||
# in the image survives the run that refuses to publish it. An SBOM you only get on a green
|
||||
# build is an SBOM you cannot use to answer "what was in the one that failed".
|
||||
- name: Generate the image SBOM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
trivy image \
|
||||
--format cyclonedx \
|
||||
--scanners license \
|
||||
--output image-sbom.cdx.json \
|
||||
"${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}"
|
||||
test -s image-sbom.cdx.json
|
||||
- name: Upload the image SBOM
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: container-release-sbom
|
||||
path: image-sbom.cdx.json
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
# The same policy dependency-vulnerability.yml applies to the filesystem, applied to the thing
|
||||
# that actually ships: CRITICAL and HIGH block, everything else is reported. The filesystem
|
||||
# scan cannot see the base image's OS packages, which is most of an image's attack surface, so
|
||||
# a green trivy-fs has never been evidence about the artifact.
|
||||
#
|
||||
# --ignorefile is mandatory here as everywhere: .trivyignore.yaml is the single suppression
|
||||
# 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.
|
||||
- name: Block High and Critical vulnerabilities in the release image
|
||||
run: |
|
||||
set -euo pipefail
|
||||
trivy image \
|
||||
--scanners vuln,license \
|
||||
--severity CRITICAL,HIGH \
|
||||
--exit-code 1 \
|
||||
--ignorefile .trivyignore.yaml \
|
||||
"${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}"
|
||||
- name: Report Medium and Low vulnerabilities in the release image
|
||||
run: |
|
||||
set -euo pipefail
|
||||
trivy image \
|
||||
--scanners vuln,license \
|
||||
--severity MEDIUM,LOW \
|
||||
--exit-code 0 \
|
||||
--ignorefile .trivyignore.yaml \
|
||||
"${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}"
|
||||
- name: Sign in to the container registry
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
printf '%s' "${REGISTRY_TOKEN}" \
|
||||
| docker login "${REGISTRY}" --username "${GITHUB_ACTOR}" --password-stdin
|
||||
# Two tags, one digest. The semver tag is what a human reads and what a release note cites; the
|
||||
# sha- tag is the one that can never be moved to different content, because the git SHA it
|
||||
# names is the only commit that can produce it.
|
||||
#
|
||||
# Neither is what a manifest should pin. Both are mutable names in a registry: a later push can
|
||||
# point `1.2.3` at something else, and nothing about a tag tells a cluster it did not. The
|
||||
# digest recorded below is immutable by construction, and it is the field the GitOps repository
|
||||
# pins — the tags exist so a person can find the digest, not so a cluster can resolve one.
|
||||
- name: Push the release and revision tags
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker push "${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}"
|
||||
docker push "${IMAGE_REPOSITORY}:${IMAGE_REVISION_TAG}"
|
||||
# awk rather than `grep | head`, deliberately. Under `set -e` with `pipefail`, a grep that
|
||||
# matches nothing exits 1 and kills the step right here — so the explicit check below,
|
||||
# and its message, would never run and the failure would surface as a bare exit code.
|
||||
# awk exits 0 whether or not it matched, which leaves the empty case for us to report.
|
||||
pinned_reference="$(
|
||||
docker image inspect \
|
||||
--format '{{range .RepoDigests}}{{println .}}{{end}}' \
|
||||
"${IMAGE_REPOSITORY}:${IMAGE_VERSION_TAG}" \
|
||||
| awk -v prefix="${IMAGE_REPOSITORY}@sha256:" \
|
||||
'index($0, prefix) == 1 { print; exit }'
|
||||
)"
|
||||
if [[ -z "${pinned_reference}" ]]; then
|
||||
echo "::error::no registry digest for ${IMAGE_REPOSITORY} after push"
|
||||
exit 1
|
||||
fi
|
||||
printf 'PINNED_REFERENCE=%s\n' "${pinned_reference}" >> "${GITHUB_ENV}"
|
||||
printf 'container-release: pushed %s\n' "${pinned_reference}"
|
||||
# The handoff to the GitOps repository, in a form a person and a script can both read. It is
|
||||
# written to the job summary as well as to an artifact because the summary is where somebody
|
||||
# looks first and the artifact is what survives the ninety days a release audit asks about.
|
||||
- name: Record the immutable image reference
|
||||
run: |
|
||||
set -euo pipefail
|
||||
digest="${PINNED_REFERENCE#*@}"
|
||||
{
|
||||
printf 'release_tag: %s\n' "${GITHUB_REF_NAME}"
|
||||
printf 'git_sha: %s\n' "${GITHUB_SHA}"
|
||||
printf 'image_repository: %s\n' "${IMAGE_REPOSITORY}"
|
||||
printf 'version_tag: %s\n' "${IMAGE_VERSION_TAG}"
|
||||
printf 'revision_tag: %s\n' "${IMAGE_REVISION_TAG}"
|
||||
printf 'digest: %s\n' "${digest}"
|
||||
printf 'pinned_reference: %s\n' "${PINNED_REFERENCE}"
|
||||
} > image-release.txt
|
||||
{
|
||||
printf '### container-release\n\n'
|
||||
printf 'Pin this in the GitOps manifest as the container image:\n\n'
|
||||
printf '```\n%s\n```\n\n' "${PINNED_REFERENCE}"
|
||||
printf -- '- release tag: `%s`\n' "${GITHUB_REF_NAME}"
|
||||
printf -- '- version tag: `%s:%s`\n' "${IMAGE_REPOSITORY}" "${IMAGE_VERSION_TAG}"
|
||||
printf -- '- revision tag: `%s:%s`\n' "${IMAGE_REPOSITORY}" "${IMAGE_REVISION_TAG}"
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
cat image-release.txt
|
||||
- name: Upload the immutable image reference
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: container-release-image-reference
|
||||
path: image-release.txt
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
@@ -1,66 +0,0 @@
|
||||
name: web-advanced-nightly
|
||||
|
||||
# Every web Advanced capability is off in production unless a deployment names it, which means none
|
||||
# of them is exercised by the ordinary PR gate. That is exactly why they need their own nightly: a
|
||||
# capability nobody runs is a capability nobody notices breaking, and the first person to find out
|
||||
# is whoever enabled it.
|
||||
#
|
||||
# The lane is tagged rather than module-scoped because Advanced lives in the same leaf as Stable.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 03:30 UTC, after web-nightly. They contend for the same machine when streaming holds
|
||||
# connections, and a load lane that shares a runner measures the runner.
|
||||
- cron: '30 3 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
web-advanced-capabilities:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the Advanced capability lane
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webAdvancedTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Prove Stable behaviour is unchanged with every flag off
|
||||
# The rollback assertion, run as its own step so a failure names itself. Two of the twelve
|
||||
# capabilities change requests that do not use them, and this is what catches a third
|
||||
# acquiring that property by accident.
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:test
|
||||
--tests '*WebAdvancedReleaseTest*'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-advanced-nightly-reports
|
||||
path: src/adapter/inbound/web/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
@@ -1,75 +0,0 @@
|
||||
name: web-advanced-release
|
||||
|
||||
# Promotion evidence for the web Advanced capabilities.
|
||||
#
|
||||
# It depends on the Stable gate rather than replacing it: the condition every Advanced capability
|
||||
# must satisfy is that Stable behaviour is unchanged with the feature off, and that is only
|
||||
# meaningful against a Stable suite that passed in the same run.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
web-advanced-promotion-evidence:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Establish the Stable baseline
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:test
|
||||
:adapter:inbound:web:webJettyCompatTest
|
||||
:adapter:inbound:web:webFluxContractTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Run the Advanced capability lane
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webAdvancedTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Verify the architecture boundary Stable depends on
|
||||
# WEB-ARCH-ADV. A feature flag decides whether an Advanced bean is created; it does nothing
|
||||
# about a Stable class that imports an Advanced type, and one such edge makes the Stable
|
||||
# platform unbuildable without the Advanced code.
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:test
|
||||
--tests '*WebArchitectureRulesTest*'
|
||||
--tests '*WebModuleBoundaryTest*'
|
||||
verifyCleanArchitectureDependencies
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the promotion evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-advanced-release-evidence
|
||||
path: src/adapter/inbound/web/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
@@ -1,55 +0,0 @@
|
||||
name: web-nightly
|
||||
|
||||
# The gates that are too slow for a pull request and too important to run only at release. Load,
|
||||
# abuse and graceful shutdown all need a machine that is not simultaneously compiling something
|
||||
# else, and all three measure behaviour that degrades gradually rather than breaking outright —
|
||||
# which is exactly the kind of regression a per-PR gate never catches and a nightly one does.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 03:00 UTC. Late enough that the day's merges are in, early enough that a failure is triaged
|
||||
# before the next working day starts.
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
web-load-abuse-and-shutdown:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the load, abuse and shutdown lanes on every container
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:test
|
||||
:adapter:inbound:web:webJettyCompatTest
|
||||
:adapter:inbound:web:webFluxContractTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-nightly-reports
|
||||
path: src/adapter/inbound/web/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
@@ -1,114 +0,0 @@
|
||||
name: web-pr
|
||||
|
||||
# Every Stable claim the web platform makes is backed by a job here. The lanes are split by what
|
||||
# they need rather than by what they test: the cross-container matrix needs three source sets, the
|
||||
# proxy contract needs Docker, and the load gate needs a machine that is not also compiling. A
|
||||
# single job running everything would attribute every failure to "the web tests".
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/adapter/inbound/web/**'
|
||||
- 'src/application-core/src/**/operation/**'
|
||||
- 'src/application-core/src/**/idempotency/**'
|
||||
- 'src/adapter/outbound/persistence-jpa/src/**/operation/**'
|
||||
- 'docs/web/**'
|
||||
- '.github/workflows/web-pr.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
web-unit-and-architecture:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the web unit, module-boundary and architecture suites
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:test
|
||||
:application-core:test
|
||||
verifyCleanArchitectureDependencies
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# The parity gate depends on all three recording lanes and fails when one is missing, so it runs
|
||||
# them itself rather than trusting a previous job to have left the recordings behind.
|
||||
web-cross-stack-parity:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Compare the wire contract across Tomcat, Jetty and Reactor Netty
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webCrossStackParityTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the parity recordings
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-contract-parity
|
||||
path: src/adapter/inbound/web/build/web-contract-parity/
|
||||
if-no-files-found: error
|
||||
|
||||
# Docker-gated, and the lane fails rather than skipping when the runtime is missing. A proxy
|
||||
# contract that quietly passes without a proxy has been certifying nothing since whenever the
|
||||
# container runtime last broke.
|
||||
web-nginx-proxy-contract:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the proxy, prefix and spoofing contract behind a real Nginx
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webNginxProxyTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -1,61 +0,0 @@
|
||||
name: web-release
|
||||
|
||||
# The complete Stable gate. Everything the PR and nightly workflows run, plus the checks whose cost
|
||||
# is only justified when something is about to ship: the public API surface, the environment key
|
||||
# registry and the whole architecture verification.
|
||||
#
|
||||
# It is one workflow rather than a reference to the others because a release gate that depends on
|
||||
# another workflow having run is a gate whose result depends on scheduling.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'web-v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
web-stable-release-gate:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run every web lane and the architecture-wide verification
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:web:webCrossStackParityTest
|
||||
:adapter:inbound:web:webNginxProxyTest
|
||||
verifyCleanArchitectureDependencies
|
||||
verifyPublicPathSnapshot
|
||||
verifyEnvKeys
|
||||
:app-bootstrap:test --tests '*CleanArchitectureTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the release evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: web-release-evidence
|
||||
path: |
|
||||
src/adapter/inbound/web/build/web-contract-parity/
|
||||
src/adapter/inbound/web/build/reports/tests/
|
||||
if-no-files-found: error
|
||||
@@ -1,64 +0,0 @@
|
||||
name: websocket-advanced-nightly
|
||||
|
||||
# The WebSocket Advanced capabilities are off unless a deployment names them, so nothing a
|
||||
# production deployment runs exercises them. A capability nobody runs is a capability nobody
|
||||
# notices breaking, and the first person to find out is whoever enables it.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 04:00 UTC, after the web lanes. Streaming and connection work contend for the same runner,
|
||||
# and a load lane sharing one measures the runner.
|
||||
- cron: '0 4 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
websocket-advanced-capabilities:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the Advanced capability lane
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:websocketAdvancedTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Verify the boundary Stable depends on
|
||||
# WS-ARCH-6. A flag decides whether an Advanced bean is created; it does nothing about a
|
||||
# Stable class that imports an Advanced type, and one such edge makes Stable unbuildable
|
||||
# without Advanced.
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:test
|
||||
--tests '*WebSocketArchitectureRulesTest*'
|
||||
--tests '*WebSocketModuleBoundaryTest*'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: websocket-advanced-nightly-reports
|
||||
path: src/adapter/inbound/websocket/build/reports/tests/
|
||||
if-no-files-found: warn
|
||||
@@ -1,98 +0,0 @@
|
||||
name: websocket-pr
|
||||
|
||||
# Every Stable claim the WebSocket platform makes is backed by a job here. The lanes are split by
|
||||
# what they need: the runtime matrix needs two containers, and the proxy contract needs Docker.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/adapter/inbound/websocket/**'
|
||||
- 'docs/websocket/**'
|
||||
- '.github/workflows/websocket-pr.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
websocket-unit-and-architecture:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the websocket unit, boundary and runtime suites
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:test
|
||||
verifyCleanArchitectureDependencies
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
websocket-container-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the runtime contract on the second servlet container
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:websocketJettyTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
|
||||
# Docker-gated, and the lane fails rather than skipping. Upgrade handling is the single most
|
||||
# common WebSocket deployment failure and it is invisible from either side alone.
|
||||
websocket-nginx-contract:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run the upgrade and forwarded-header contract behind a real Nginx
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:websocketNginxTest
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
@@ -1,56 +0,0 @@
|
||||
name: websocket-release
|
||||
|
||||
# The complete Stable gate: every lane plus the architecture-wide verification. One workflow rather
|
||||
# than a reference to the others, because a release gate that depends on another workflow having run
|
||||
# is a gate whose result depends on scheduling.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'websocket-v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
websocket-stable-release-gate:
|
||||
runs-on: ubuntu-latest
|
||||
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: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21.0.11+10"
|
||||
cache: gradle
|
||||
cache-dependency-path: |
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Run every websocket lane and the architecture-wide verification
|
||||
working-directory: src
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:inbound:websocket:test
|
||||
:adapter:inbound:websocket:websocketJettyTest
|
||||
:adapter:inbound:websocket:websocketNginxTest
|
||||
:adapter:inbound:websocket:websocketTransportQualificationTest
|
||||
verifyCleanArchitectureDependencies
|
||||
:app-bootstrap:test --tests '*CleanArchitectureTest'
|
||||
--no-daemon
|
||||
--stacktrace
|
||||
- name: Publish the release evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: websocket-release-evidence
|
||||
path: src/adapter/inbound/websocket/build/reports/tests/
|
||||
if-no-files-found: error
|
||||
+13
-5
@@ -1,13 +1,21 @@
|
||||
# Structured Trivy suppression baseline.
|
||||
#
|
||||
# This repository-root file is the only CI suppression source. Every future entry must include:
|
||||
# This repository-root file is the only CI suppression source. Every Trivy invocation must name it
|
||||
# with `--ignorefile .trivyignore.yaml`; ad-hoc ignore files and inline bypasses are not allowed.
|
||||
#
|
||||
# Every entry must carry:
|
||||
# id: advisory, license, misconfiguration, or secret identifier
|
||||
# statement: non-empty accepted-risk or false-positive rationale
|
||||
# expired_at: future YYYY-MM-DD no more than 90 days from review
|
||||
# expired_at: future YYYY-MM-DD, no more than 90 days from review
|
||||
#
|
||||
# `verifyTrivyignore` enforces those fields and the expiry window. CODEOWNERS supplies the separate
|
||||
# reviewer control. Every Trivy invocation must also name this file with
|
||||
# `--ignorefile .trivyignore.yaml`; do not add ad-hoc ignore files or inline bypasses.
|
||||
# Enforced by review, not by a build task. `verifyTrivyignore` used to be a 105-line hand-written
|
||||
# YAML parser in the root build — indentation tracking, inline-scalar handling, quote stripping — and
|
||||
# what it guarded was this file, which has been empty since it was created. A suppression is added by
|
||||
# a human and merged by a CODEOWNERS reviewer (.github/dependency-vulnerability-policy.md); that
|
||||
# reviewer is the control, and a parser that has never seen an entry is not a second one.
|
||||
#
|
||||
# If this file ever carries entries and they start drifting, that is the moment to automate the
|
||||
# check — against real entries, with a real YAML library. Not before.
|
||||
|
||||
vulnerabilities: []
|
||||
licenses: []
|
||||
|
||||
@@ -52,10 +52,19 @@ root `CLAUDE.md`는 이 목록의 동기화된 요약이다. 두 문서가 어
|
||||
- `src/config/architecture/modules.json`: 등록된 모든 leaf의 ID, repository-relative 소스 경로,
|
||||
Gradle path, 허용 production project dependency edge, 두 composition root의 실제 runtime
|
||||
membership. leaf 목록과 그 개수의 SSOT는 registry다. 문서는 개수를 복제하지 않는다 —
|
||||
산문에 적힌 숫자는 leaf가 추가되는 순간 drift한다. `verifyDocumentedLeafCount`가 이를 강제한다.
|
||||
- `src/settings.gradle`: registry를 fail-closed로 검증하고 등록된 Gradle project를 include/mapping
|
||||
- `src/build.gradle`: 같은 registry를 읽는 `verifyCleanArchitectureDependencies`와 그 밖의
|
||||
architecture-wide verification task
|
||||
산문에 적힌 숫자는 leaf가 추가되는 순간 drift하기 때문이다. 이제 이걸 강제하는 태스크는 없다:
|
||||
`verifyDocumentedLeafCount`는 삭제됐다. 문서에 적힌 수가 틀린 것은 결함이지만 빌드를 실패시킬
|
||||
사유는 아니고, 그 태스크는 모든 `CLAUDE.md`와 `build.gradle`을 정규식으로 훑는 파서였다.
|
||||
- `src/settings.gradle`: 16줄. `ca.architecture-registry` 설정 플러그인이 registry를 읽어
|
||||
project를 include/mapping 한다. registry가 project 목록이 될 수 없는 경우(중복 ID, 저장소 밖
|
||||
경로, 없는 디렉터리)만 여기서 실패한다. 허용되지 않는 edge 같은 아키텍처 규칙은
|
||||
`verifyCleanArchitectureDependencies`가 답한다 — settings에서 죽으면 실행할 수 있는 태스크가
|
||||
하나도 없다.
|
||||
- `src/build-logic/`: convention plugin. leaf는 `ca.java-library` / `ca.spring-library` /
|
||||
`ca.platform-module` 중 자기 성격을 선언하고, 그 플러그인이 toolchain·락·정적분석·테스트
|
||||
기본값을 준다. `ca.architecture`가 아키텍처 검증 태스크를 소유한다.
|
||||
- `src/build.gradle`: 루트 라이프사이클(`ci`, `releaseCheck`, `qualityCheck`,
|
||||
`configContractCheck`, `integrationCheck`)과 버전/리비전
|
||||
|
||||
작업 파일의 소유 leaf는 registry의 `source_path`로 판단하고 가장 가까운 `src/**/CLAUDE.md`를
|
||||
함께 읽는다. focused test는 registry의 `gradle_path`에서
|
||||
@@ -174,14 +183,17 @@ Gradle 의존성 검증도 같은 registry를 읽는다. root 문서나 기억
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew <owner-gradle-path>:test --console=plain
|
||||
./gradlew test
|
||||
./gradlew check # check 가 verifyCleanArchitectureDependencies + verifyEnvKeys 2종을 전이 실행한다 (src/build.gradle)
|
||||
./gradlew verifyCleanArchitectureDependencies
|
||||
./gradlew <owner-gradle-path>:check --console=plain # 그 leaf만: 컴파일·테스트·포맷·스타일·ErrorProne
|
||||
./gradlew check # 모든 leaf의 check
|
||||
./gradlew architectureCheck # 의존 방향·런타임 멤버십·application-core 순수성
|
||||
./gradlew qualityCheck # SpotBugs + FindSecBugs (leaf check에는 없다)
|
||||
./gradlew ci # PR 게이트 = 위 셋 + configContractCheck
|
||||
./gradlew verifyPublicPathSnapshot
|
||||
./gradlew verifyEnvKeys
|
||||
./gradlew :app-bootstrap:verifyEnvKeys
|
||||
```
|
||||
|
||||
leaf의 `check`는 그 leaf만 검사한다. 저장소 전체 질문은 이름이 따로 있는 루트 태스크가 답한다.
|
||||
|
||||
소유 leaf의 정확한 Gradle path는 `src/config/architecture/modules.json`에서 읽고 focused test
|
||||
명령을 파생한다. root 문서에 leaf별 명령 목록을 복제하지 않는다.
|
||||
|
||||
|
||||
@@ -45,8 +45,9 @@ count.
|
||||
## Module families
|
||||
|
||||
`src/config/architecture/modules.json` owns the complete leaf list. Root guidance summarizes
|
||||
families; the nearest `src/**/CLAUDE.md` owns local rules. `verifyDocumentedLeafCount` fails the
|
||||
build when a policy document states a leaf count that the registry does not agree with.
|
||||
families; the nearest `src/**/CLAUDE.md` owns local rules. No task enforces this any more:
|
||||
`verifyDocumentedLeafCount` was deleted along with the other documentation-drift parsers. A stated
|
||||
count that disagrees with the registry is a defect, not a build failure — so do not state one.
|
||||
|
||||
| Family | Responsibility | Stable dependency direction |
|
||||
| --- | --- | --- |
|
||||
@@ -105,14 +106,21 @@ From `src/`, read the owning leaf's `gradle_path` from
|
||||
Architecture-wide commands:
|
||||
|
||||
```bash
|
||||
./gradlew verifyCleanArchitectureDependencies --console=plain
|
||||
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
|
||||
./gradlew architectureCheck --console=plain
|
||||
./gradlew :app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*' --console=plain
|
||||
./gradlew verifyPublicPathSnapshot --console=plain
|
||||
./gradlew verifyEnvKeys --console=plain
|
||||
./gradlew :app-bootstrap:verifyEnvKeys --console=plain
|
||||
```
|
||||
|
||||
Use public-path and env-key checks only when their surfaces changed. Full `test` or `check` requires
|
||||
the controller's workflow authorization.
|
||||
A leaf's `check` covers that leaf only — compile, its tests, Spotless, Checkstyle, Error Prone.
|
||||
Repository-wide questions have their own names: `architectureCheck` (dependency direction, runtime
|
||||
membership, application-core purity, Git-carryable sources), `qualityCheck` (SpotBugs, FindSecBugs),
|
||||
`configContractCheck` (the environment contract), `integrationCheck` (the declared strict test
|
||||
lanes). `ci` is check + architectureCheck + qualityCheck + configContractCheck; `releaseCheck` adds
|
||||
provenance, archive hygiene and the public-path snapshot.
|
||||
|
||||
Use public-path and env-key checks only when their surfaces changed. Full `test`, `check` or `ci`
|
||||
requires the controller's workflow authorization.
|
||||
|
||||
## Advisory and reporting
|
||||
|
||||
|
||||
@@ -122,6 +122,35 @@ cd src
|
||||
|
||||
두 검증 축은 [ci-quality-gates.yml](.github/workflows/ci-quality-gates.yml)의 release gate에 연결되어, 규칙 위반이 병합·릴리스를 막습니다.
|
||||
|
||||
## 수동 전용 Gradle 태스크
|
||||
|
||||
아래 세 태스크는 **어떤 워크플로도 실행하지 않으며, 그게 의도다.** 자동 실행이 틀린 이유를 각각
|
||||
적어 둔다.
|
||||
|
||||
여기 적힌 태스크 이름이 실재하는지 검사하던 `verifyReadmeCommands`는 삭제했다. 그건 이 문서의
|
||||
```bash 블록을 직접 파싱해 `./gradlew`·`docker compose`·`make` 토큰을 실제 태스크 그래프와 대조하는
|
||||
Markdown 명령 파서였고, 그 결과 "README에 무엇을 쓸 수 있는가"가 그 파서가 읽을 수 있는 문법의
|
||||
함수가 됐다. 문서와 코드가 어긋나는 것은 결함이지만, 빌드를 실패시켜서 고칠 일은 아니다.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
./gradlew :grpc:grpc-testkit:grpcPerformanceTest
|
||||
./gradlew :sample-portfolio:openapiCheckSnapshot -PapproveOpenApiChange
|
||||
./gradlew :app-bootstrap:sampleOffCompile
|
||||
```
|
||||
|
||||
- `grpcPerformanceTest` — latency percentile·saturation·drain budget을 **측정**한다. 공유 CI
|
||||
runner의 측정값은 흔들리고, 흔들리는 게이트는 결국 꺼진다. leaf `build.gradle`이 이 태스크의
|
||||
태그를 `test`에서 제외하는 이유도 같다. 성능 회귀가 의심될 때 사람이 이름으로 부른다.
|
||||
- `openapiCheckSnapshot` — 드리프트 검사 자체는 이미 자동으로 돈다. 이 태스크가 감싸는
|
||||
`OpenApiDriftContractTest`는 `:sample-portfolio:test`의 일부이고, 그건 `check` 안이며 stage 1에서
|
||||
실행된다. 이 태스크의 고유한 역할은 `-PapproveOpenApiChange`로 **커밋된 스냅샷을 다시 만드는 것**
|
||||
— 의도된 API 변경을 사람이 승인하는 지점이다. 자동으로 돌리면 승인이 승인이 아니게 된다.
|
||||
- `sampleOffCompile` — `sampleOffTest` 소스셋을 **컴파일만** 한다. CI가 돌리는
|
||||
`:app-bootstrap:sampleOffTest`(stage 1, `ci-quality-gates.yml`의 `sample-off` 잡)는 같은 소스셋을
|
||||
컴파일한 뒤 실행까지 하므로, CI에 따로 넣으면 진부분집합을 한 번 더 도는 것이다. 남겨 둔 이유는
|
||||
sample 제거 작업 중 테스트를 기다리지 않고 컴파일만 빠르게 확인하는 로컬 루프가 실재하기 때문이다.
|
||||
|
||||
## 더 알아보기
|
||||
|
||||
- 빌드·검증 게이트·환경 변수 상세: [src/README.md](src/README.md)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
# ADR-BUILD-001: `java-test-fixtures` is the standard for shared test code
|
||||
|
||||
- Status: Accepted
|
||||
- Date: 2026-09-07
|
||||
- Scope: every leaf that publishes or consumes shared test code
|
||||
- Source: `docs/reviews/2026-09-07-app-bootstrap-module-code-review.md` BOOT-015
|
||||
|
||||
## Context
|
||||
|
||||
Two conventions do the same job in this repository.
|
||||
|
||||
`ca.testkit-publisher` — a convention plugin — gives a leaf a `testkit` source set, wires its output
|
||||
onto the lanes that leaf names, and optionally publishes it as a consumable configuration. Five
|
||||
leaves use it: `persistence-jpa` (published as `jpaTestkit`), `web` (`webTestkit`), `websocket`
|
||||
(`websocketTestkit`), `persistence-mongo` and `httpclient` (both unpublished).
|
||||
|
||||
`java-test-fixtures` — Gradle's own plugin — gives a leaf a `testFixtures` source set, puts it on
|
||||
`test`'s classpath automatically, and always publishes it as a variant consumers reach with
|
||||
`testFixtures(project(':x'))`. One leaf uses it: `graphql`, which additionally fails its build when a
|
||||
fixture is written outside `src/testFixtures/java`.
|
||||
|
||||
Two conventions for one purpose is the defect. A contributor adding shared test code has to know
|
||||
which leaf they are in before they know where the file goes, and the two answers are not
|
||||
interchangeable: a consumer of the first writes `project(path: ':x', configuration: 'jpaTestkit')`
|
||||
and has to know the configuration's name, while a consumer of the second writes
|
||||
`testFixtures(project(':x'))` and does not.
|
||||
|
||||
## Decision
|
||||
|
||||
**`java-test-fixtures` is the standard.** New shared test code goes in `src/testFixtures/java`, and a
|
||||
consumer depends on it with `testFixtures(project(':x'))`.
|
||||
|
||||
Three reasons, in order of weight:
|
||||
|
||||
1. **The consumer side describes itself.** `testFixtures(project(':adapter:inbound:web'))` says what
|
||||
it is. `project(path: ':adapter:inbound:web', configuration: 'webTestkit')` says where to look,
|
||||
and only after the reader has learned that `webTestkit` is a testkit rather than a lane.
|
||||
2. **The enforcement already exists and is copyable.** `graphql`'s build fails when a fixture is
|
||||
declared in the wrong place. The same guard applies unchanged to any leaf that adopts the plugin.
|
||||
3. **It is one fewer local concept.** A convention plugin that reimplements a Gradle plugin has to be
|
||||
maintained against it.
|
||||
|
||||
## What the local plugin does better, and how it is replaced
|
||||
|
||||
This is worth writing down, because the review that prompted this ADR recommended the migration
|
||||
before reading `ca.testkit-publisher`, and the plugin turns out to encode two deliberate decisions
|
||||
rather than being an oversight.
|
||||
|
||||
**Publishing is opt-in.** `persistence-mongo` and `httpclient` have a testkit and publish nothing;
|
||||
`persistence-jpa` publishes. The plugin's own comment names this as "a real difference in what each
|
||||
leaf offers rather than an oversight to normalise away". `java-test-fixtures` always creates the
|
||||
variant, so the distinction is lost — a leaf that never meant to offer its fixtures will offer them.
|
||||
|
||||
> Replacement: none at the build level. The distinction moves to review: the fixtures of a leaf that
|
||||
> nobody consumes are simply unconsumed. This is a real, accepted loss.
|
||||
|
||||
**Lane consumption is declared.** `persistence-jpa` says `consumedBy 'test', 'postgresqlIntegrationTest'`.
|
||||
`java-test-fixtures` puts fixtures on `test` only, so every other lane needs the output added
|
||||
explicitly.
|
||||
|
||||
> Replacement: `strictTestLanes`' existing `compilesAgainst` expresses this unchanged — a lane
|
||||
> declares `compilesAgainst 'main', 'testFixtures'`. The first draft of this ADR assumed the DSL
|
||||
> would need a change, because `sourceSet(name)` creates what it is given and `testFixtures` already
|
||||
> exists. The `persistence-mongo` migration showed otherwise: `compilesAgainst` only *looks a source
|
||||
> set up*, so naming a plugin-created one works as-is. What the leaf drops is the
|
||||
> `sourceSet('testkit')` declaration, not the lane's.
|
||||
|
||||
## Migration: done, and what it cost
|
||||
|
||||
Five leaves, eleven lanes, two published testkits, all migrated leaf by leaf with the suite run
|
||||
between each. `ca.testkit-publisher` is deleted.
|
||||
|
||||
The order was chosen so a mistake would be cheap: unpublished leaves first, published ones last with
|
||||
their consumer in the same step.
|
||||
|
||||
1. `persistence-mongo` — one leaf, two lanes, no cross-module consumer; the proof the path works.
|
||||
What it took, per leaf:
|
||||
- `apply plugin: 'java-test-fixtures'` at the top of the leaf build file;
|
||||
- `git mv src/testkit src/testFixtures`;
|
||||
- drop `sourceSet('testkit')` and the whole `testkitPublisher` block; keep every other lane's
|
||||
`compilesAgainst`, renaming `'testkit'` to `'testFixtures'`;
|
||||
- rename `testkitImplementation` to `testFixturesImplementation`, **and add what the old source
|
||||
set was inheriting silently**. This is the one non-mechanical step: `testkit*` extended
|
||||
`testImplementation`, so the fixtures saw every test library the leaf declared. Mongo's needed
|
||||
four more lines (AssertJ, BSON, Spring Data commons, Toxiproxy) — none of which the leaf had
|
||||
ever stated the fixtures depended on;
|
||||
- regenerate the leaf's lock state.
|
||||
2. `httpclient`, then `websocket` — unpublished as well, more lanes.
|
||||
3. `web` and `persistence-jpa` with `app-bootstrap`'s two consumer declarations, which became
|
||||
`testImplementation(testFixtures(project(':…')))`.
|
||||
4. `ca.testkit-publisher` deleted, along with its `plugins {}` entry and its application in the root
|
||||
build.
|
||||
|
||||
### Two things the migration broke, and what they taught
|
||||
|
||||
Both were caught by tests that exist to catch exactly this, which is the argument for having them.
|
||||
|
||||
**ArchUnit corpora went wrong in opposite directions.** `httpclient`'s boundary rules *excluded*
|
||||
`build/classes/java/testkit`; after the move the fixtures arrived as a `…-test-fixtures.jar` on the
|
||||
same classpath, so the exclusion missed them and 258 fixture-to-fixture calls were reported as
|
||||
production depending on the testkit. `persistence-jpa`'s rules *included* only
|
||||
`build/classes/java/main`; applying `java-test-fixtures` makes the module's own test classpath carry
|
||||
the module as a **jar** rather than as a class directory, so its corpus became empty. The second is
|
||||
the dangerous one — an empty corpus makes every `noClasses()` rule pass — and it surfaced only
|
||||
because that suite asserts its corpus is non-empty before asserting anything about it.
|
||||
|
||||
**Fixtures had invisible dependencies.** `testkit*` configurations extended `testImplementation`, so
|
||||
the fixtures compiled against every test library their leaf declared without ever naming one. Making
|
||||
them explicit took roughly thirty `testFixturesImplementation` lines across the five leaves —
|
||||
Micrometer, Spring Web, Netty, logback, Jackson, JUnit, AssertJ, Spring Data. None of them were
|
||||
wrong; none of them were stated.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `docs/testing/TESTING_STRATEGY.md` §5 records the standard; this ADR records why and at what cost.
|
||||
- Until step 5, two conventions remain visible. The strategy document says so explicitly, so a
|
||||
contributor reading it is not left to infer which one is current.
|
||||
- The opt-in-publishing distinction is given up. If it later proves load-bearing — a leaf whose
|
||||
fixtures genuinely must not be reachable — the answer is a separate module, not a third convention.
|
||||
@@ -0,0 +1,73 @@
|
||||
# CI/CD 경계 — GitHub Actions는 CI, Argo CD는 CD
|
||||
|
||||
## 결론
|
||||
|
||||
GitHub Actions는 **검증하고 아티팩트를 만든다**. Argo CD는 **배포한다**. 두 역할은 겹치지 않는다.
|
||||
|
||||
GitHub Actions 워크플로는 `kubectl apply`, `helm upgrade`, `argocd app sync` 중 어느 것도 하지
|
||||
않는다. 그러므로 CI에는 클러스터 자격증명(kubeconfig, 서비스 계정 토큰)이 들어가지 않는다.
|
||||
|
||||
## 흐름
|
||||
|
||||
```text
|
||||
git push / tag
|
||||
│
|
||||
▼
|
||||
GitHub Actions ─────────────── CI ───────────────┐
|
||||
• 테스트 · 정적분석 · 아키텍처 검증 │
|
||||
• 컨테이너 이미지 빌드 │
|
||||
• 취약점 스캔 (Trivy) │
|
||||
• SBOM 생성 │
|
||||
• 레지스트리에 이미지 push │
|
||||
│ │
|
||||
│ 이미지 태그(다이제스트)를 manifest에 기록 │
|
||||
▼ │
|
||||
GitOps 저장소 (배포 희망 상태) ──────────────────┘
|
||||
│
|
||||
│ Argo CD가 watch
|
||||
▼
|
||||
Argo CD ──────────────────── CD ───────────────
|
||||
│ auto-sync
|
||||
▼
|
||||
Kubernetes
|
||||
```
|
||||
|
||||
용어 한 줄 풀이:
|
||||
|
||||
- **GitOps 저장소** — 클러스터에 무엇이 떠 있어야 하는지를 적어 둔 Git 저장소. 애플리케이션 소스와
|
||||
분리한다.
|
||||
- **manifest** — Kubernetes에 넣을 YAML(Deployment, Service 등).
|
||||
- **auto-sync** — Argo CD가 GitOps 저장소의 변경을 스스로 감지해 클러스터에 반영하는 모드. 이걸 쓰면
|
||||
CI가 Argo CD API 서버에 접근할 필요가 없다.
|
||||
|
||||
## 왜 이렇게 나누나
|
||||
|
||||
1. **자격증명 반경.** CI가 배포하면 CI 러너가 프로덕션 클러스터에 대한 쓰기 권한을 갖는다. 포크된
|
||||
PR, 서드파티 액션, 캐시 오염이 모두 그 권한에 닿는다. auto-sync를 쓰면 그 권한은 클러스터 안의
|
||||
Argo CD에만 있고, CI는 Git에 커밋만 한다.
|
||||
2. **현재 상태의 소유자가 하나.** 클러스터에 무엇이 떠 있는지는 GitOps 저장소가 답한다. CI가 직접
|
||||
apply 하면 답이 두 개가 된다 — Git에 적힌 것과 실제로 떠 있는 것.
|
||||
3. **롤백이 revert.** 배포를 되돌리는 것이 `git revert`가 된다.
|
||||
|
||||
## 이 저장소의 현재 위치
|
||||
|
||||
| 항목 | 상태 |
|
||||
| --- | --- |
|
||||
| 이미지 빌드/스캔/push | `release.yml`이 수행 |
|
||||
| SBOM | `release.yml`이 생성 |
|
||||
| 이미지 서명 · provenance attestation | **없음.** 추가 대상 |
|
||||
| GitOps 저장소 | **없음.** 별도 저장소로 만들 예정 |
|
||||
| Argo CD Application 정의 | **없음.** GitOps 저장소에 둘 예정 |
|
||||
| CI에서의 클러스터 접근 | 없음 — 유일했던 `kubectl apply`는 제거됨 |
|
||||
|
||||
`fileserver-certification.yml`은 예외처럼 보이지만 아니다. PVC 매니페스트가 여전히 ReadWriteOnce를
|
||||
선언하는지 **파일만** 확인하고, 클러스터에는 아무것도 적용하지 않는다. 실제 클러스터에서의 인증은
|
||||
운영자가 `infra/fileserver/kubernetes/pvc-certification-job.yaml`을 직접 실행하고
|
||||
`docs/fileserver/storage-certification.md`에 기록한다. 이름을 `fileserver-release.yml`에서 바꾼 이유가
|
||||
이것이다 — 이 워크플로는 릴리스하지 않는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
- 워크플로에 클러스터 자격증명 secret을 추가하지 않는다.
|
||||
- 배포 대상이 바뀌면 GitOps 저장소의 manifest를 바꾼다. 워크플로를 바꾸지 않는다.
|
||||
- CI가 만드는 것은 **불변 다이제스트로 지정된 이미지**다. `latest` 태그로 배포하지 않는다.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Template maintainer와 Template consumer의 검증은 다르다
|
||||
|
||||
## 결론
|
||||
|
||||
이 저장소에는 성격이 다른 두 종류의 검증이 섞여 있다.
|
||||
|
||||
1. **스켈레톤을 만드는 사람**에게 필요한 검증 — sample 모듈이 정말 제거 가능한가, optional 모듈
|
||||
조합이 모두 빌드되는가, 레지스트리가 확장 가능한가.
|
||||
2. **스켈레톤을 가져다 서비스를 만드는 사람**에게 필요한 검증 — 내 애플리케이션의 테스트,
|
||||
아키텍처 방향, 보안, 릴리스.
|
||||
|
||||
파생 프로젝트가 1번을 그대로 물려받으면, 자기 서비스와 아무 상관 없는 게이트를 평생 유지하게 된다.
|
||||
이 문서는 어느 쪽이 어느 쪽인지 적어 둔다.
|
||||
|
||||
## Template 전용 (파생 프로젝트는 삭제해도 된다)
|
||||
|
||||
| 대상 | 무엇을 지키는가 |
|
||||
| --- | --- |
|
||||
| `:app-bootstrap:sampleOffTest`, `ci-quality-gates.yml`의 `sample-off` job | sample 픽스처를 지워도 애플리케이션이 빌드·부팅되는가 |
|
||||
| `sample-portfolio` leaf 전체 | 참조 구현 |
|
||||
| `Dockerfile.sample`, `docker-compose.*` 중 sample 관련 | 위와 동일 |
|
||||
| `docs/superpowers/**` | 이 템플릿을 만든 과정의 설계/계획 기록 |
|
||||
| `gradle/qualification/**` | 이 템플릿이 벤더링한 플랫폼(JPA, messaging)의 인증 체계 |
|
||||
| `*-certification.yml`, `*-qualification.yml`, `jpa-next-*.yml` | 템플릿이 광고하는 지원 매트릭스의 근거 |
|
||||
|
||||
## Consumer 필수 (파생 프로젝트가 유지해야 한다)
|
||||
|
||||
| 대상 | 무엇을 지키는가 |
|
||||
| --- | --- |
|
||||
| `architectureCheck` | Clean Architecture 의존 방향. 이 템플릿의 존재 이유 |
|
||||
| 각 leaf의 `check` | 컴파일 · 단위 테스트 · 포맷 · 스타일 · Error Prone |
|
||||
| `qualityCheck` | SpotBugs / FindSecBugs |
|
||||
| `configContractCheck` | 환경변수 계약 |
|
||||
| `verifyDependencyLocks` | 재현 가능한 의존성 해석 |
|
||||
| `dependency-vulnerability.yml` | dependency-review + Trivy |
|
||||
| `ci-quality-gates.yml` | PR 게이트 |
|
||||
| `release.yml` | 이미지 · SBOM 생산 |
|
||||
| action의 full SHA 핀 | 공급망 |
|
||||
|
||||
## 파생 프로젝트가 할 일
|
||||
|
||||
1. Template 전용 표의 항목을 삭제한다. 삭제는 대부분 파일 삭제 + `config/architecture/modules.json`
|
||||
에서 leaf 항목 제거로 끝난다 — 레지스트리가 leaf 목록의 SSOT이고, 개수를 따로 적어 둔 곳은 없다.
|
||||
2. `docs/ci-cd/boundary.md`의 경계를 그대로 유지한 채 자기 GitOps 저장소를 연결한다.
|
||||
3. `.trivyignore.yaml`과 CODEOWNERS는 그대로 쓴다.
|
||||
|
||||
## 아직 하지 않은 것
|
||||
|
||||
Template CI와 Generated Application CI를 **물리적으로** 분리하지는 않았다(생성기 없음). 지금은 이
|
||||
문서가 그 경계다. 생성기를 만든다면, 위 표의 "Template 전용" 열이 생성기가 벗겨 내야 할 목록이다.
|
||||
@@ -12,7 +12,7 @@
|
||||
#
|
||||
# Only APP_HTTPCLIENT_ENABLED is registered in docs/registries/env-keys.yaml and shipped in
|
||||
# src/.env: it is the only key with a deployment-independent value, and it is the only one the
|
||||
# three-way verifyEnvKeys gate can express. Everything below is per deployment and is set directly
|
||||
# three-way :app-bootstrap:verifyEnvKeys gate can express. Everything below is per deployment and is set directly
|
||||
# in the environment — templating an indexed client in application.yml would materialise a nameless
|
||||
# client in every deployment, which the settings' aggregate validation refuses.
|
||||
#
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
```bash
|
||||
./gradlew verifyCleanArchitectureDependencies --console=plain
|
||||
./gradlew verifyRuntimeModuleMembership --console=plain
|
||||
./gradlew verifyOneTypePerFile --console=plain
|
||||
./gradlew checkstyleMain --console=plain
|
||||
```
|
||||
|
||||
destination profile은 startup에서 검증된다. 아래는 **부팅 실패**다.
|
||||
|
||||
@@ -32,6 +32,8 @@ dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.Notification
|
||||
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.ProviderRuntimeAssembler
|
||||
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.ProviderType
|
||||
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.SmtpProviderRuntimeAssembler
|
||||
dev.caskeleton.adapter.outbound.notification.platform.callback.MapProviderCallbackAdapterRegistry
|
||||
dev.caskeleton.adapter.outbound.notification.platform.callback.MapProviderEventProjectorRegistry
|
||||
dev.caskeleton.adapter.outbound.notification.platform.dispatch.AttemptPermit
|
||||
dev.caskeleton.adapter.outbound.notification.platform.dispatch.CapabilityReconciliationGateway
|
||||
dev.caskeleton.adapter.outbound.notification.platform.dispatch.ConfiguredProfileCatalog
|
||||
@@ -57,6 +59,7 @@ dev.caskeleton.adapter.outbound.notification.platform.dispatch.SingleTenantConte
|
||||
dev.caskeleton.adapter.outbound.notification.platform.dispatch.UuidV7Generator
|
||||
dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotificationAudit
|
||||
dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotificationMetrics
|
||||
dev.caskeleton.adapter.outbound.notification.platform.observation.MicrometerNotificationMetrics
|
||||
dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter
|
||||
dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthSnapshot
|
||||
dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationServingThresholds
|
||||
|
||||
@@ -1897,7 +1897,7 @@ env_keys:
|
||||
# Bound only by RedisSdkAutoConfiguration, which exists only while APP_REDIS_ENABLED
|
||||
# is true. They are deliberately absent from application.yml and src/.env: putting
|
||||
# them there would make a Redis-free deployment carry Redis configuration, which is
|
||||
# the defect the conditional composition root removes. verifyEnvKeys checks them
|
||||
# the defect the conditional composition root removes. :app-bootstrap:verifyEnvKeys checks them
|
||||
# against spring-configuration-metadata.json instead.
|
||||
|
||||
- name: APP_REDIS_ACKNOWLEDGED_WRITE_LOSS_ACCEPTED
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
# Messaging R2 자격(qualification) — 미구현
|
||||
|
||||
추적: MSG-015
|
||||
|
||||
## 상태
|
||||
|
||||
**구현되지 않았다.** R2 자격을 주장할 수 있는 근거가 없다.
|
||||
|
||||
- qualification producer 없음
|
||||
- 대응하는 Test 태스크 없음
|
||||
- 공통 스키마 validator 없음
|
||||
|
||||
따라서 `config/messaging/readiness-cards.yaml`의 카드는 `verifyMessagingContracts`와
|
||||
`verifyMessagingJsonSchemaV1` 두 개를 제외하면 모두 `maturity: not-implemented`다.
|
||||
|
||||
## 왜 Gradle 태스크를 미리 만들어 두지 않는가
|
||||
|
||||
2026-09 이전에는 루트 빌드가 아래 아홉 개 태스크 이름을 미리 등록해 두고, 그 본문이 **입력과 무관하게
|
||||
무조건 예외를 던졌다**.
|
||||
|
||||
```text
|
||||
verifyMessagingPollingOutboxR2 verifyMessagingTargetBinding
|
||||
verifyMessagingKafkaProducerR2 verifyMessagingDeploymentCutover
|
||||
verifyMessagingSecurityR2 verifyMessagingCleanupTargetBinding
|
||||
verifyMessagingReleaseProfile verifyMessagingFinalR2Profile
|
||||
verifyMessagingTargetBindingPreflight
|
||||
```
|
||||
|
||||
의도는 "fail-closed"였지만 결과는 다음과 같았다.
|
||||
|
||||
- `./gradlew tasks`에 게이트처럼 보이는 이름 아홉 개가 나타난다.
|
||||
- `dependsOn`으로 걸 수 있다. 거는 순간 그 레인은 영원히 빨간불이다.
|
||||
- 정상적인 입력으로도 성공할 수 없으므로 "검증"이 아니다.
|
||||
|
||||
즉 TODO를 Gradle 태스크 API로 표현한 것이었다. 미구현 사실을 기록하는 자리는 이 문서이고, 태스크는
|
||||
**실제로 통과할 수 있게 된 시점에** 그 producer와 함께 추가한다.
|
||||
|
||||
## 구현 시 추가할 것
|
||||
|
||||
1. 각 시나리오를 실제로 실행하는 Test 태스크.
|
||||
2. 그 실행 결과(JUnit XML)에서 payload-free manifest를 만드는 producer.
|
||||
3. `config/messaging/evidence/build-evidence-manifest-v1.schema.json`으로 그 manifest 바이트를
|
||||
검증하는 finalizer.
|
||||
4. 위 셋이 모두 생긴 다음에 `verifyMessaging<Scenario>R2` 태스크 등록.
|
||||
|
||||
`gradle/qualification/messaging-qualification.gradle`의 `verifyMessagingJsonSchemaV1`이 그 네 단계를
|
||||
모두 갖춘 예시다.
|
||||
@@ -1,4 +1,6 @@
|
||||
# feature-security-operational-baseline D5 — deny-by-default public path snapshot.
|
||||
# SSOT: SECURITY_PUBLIC_PATHS (src/.env) -> SecurityConfig permitAll(); anyRequest authenticated.
|
||||
# SSOT: ca-skeleton.security.public-paths default in app-bootstrap/src/main/resources/config/security.yml
|
||||
# -> SecurityConfig permitAll(); anyRequest authenticated. An operator's own SECURITY_PUBLIC_PATHS
|
||||
# overrides it at run time and is outside this snapshot.
|
||||
# Update only after review with: ./gradlew updatePublicPathSnapshot -PapprovePublicPathChange
|
||||
/api/healthcheck
|
||||
/v1/healthcheck
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,7 +79,7 @@ Run:
|
||||
--tests 'dev.caskeleton.application.outbox.*' --console=plain
|
||||
./gradlew :adapter:outbound:messaging:test --console=plain
|
||||
./gradlew :app-bootstrap:test \
|
||||
--tests 'dev.caskeleton.bootstrap.outbox.*' --console=plain
|
||||
--tests 'dev.caskeleton.bootstrap.autoconfigure.outbox.*' --console=plain
|
||||
./gradlew verifyApplicationCoreDependencyPurity --console=plain
|
||||
./gradlew :application-core:dependencies \
|
||||
--configuration runtimeClasspath --console=plain
|
||||
|
||||
@@ -2899,12 +2899,12 @@ authority and destructive downgrade are forbidden.
|
||||
dev.caskeleton.adapter.outbound.persistence.outbox.OutboxClaimRepository
|
||||
dev.caskeleton.adapter.outbound.persistence.postgresql.PostgreSqlOutboxClaimRepository
|
||||
dev.caskeleton.adapter.outbound.persistence.outbox.OutboxReaper
|
||||
dev.caskeleton.bootstrap.outbox.OutboxLeaderElectionToken
|
||||
dev.caskeleton.bootstrap.outbox.OutboxLegacyToV2CutoverCoordinator
|
||||
dev.caskeleton.bootstrap.outbox.OutboxLegacyPreCommitRecoveryCoordinator
|
||||
dev.caskeleton.bootstrap.outbox.LegacyOutboxRelayControlAdapter
|
||||
dev.caskeleton.bootstrap.outbox.MessagingAuthorityCutoverJobSettings
|
||||
dev.caskeleton.bootstrap.outbox.MessagingAuthorityCutoverApplicationRunner
|
||||
dev.caskeleton.bootstrap.autoconfigure.outbox.OutboxLeaderElectionToken
|
||||
dev.caskeleton.bootstrap.autoconfigure.outbox.OutboxLegacyToV2CutoverCoordinator
|
||||
dev.caskeleton.bootstrap.autoconfigure.outbox.OutboxLegacyPreCommitRecoveryCoordinator
|
||||
dev.caskeleton.bootstrap.autoconfigure.outbox.LegacyOutboxRelayControlAdapter
|
||||
dev.caskeleton.bootstrap.autoconfigure.outbox.MessagingAuthorityCutoverJobSettings
|
||||
dev.caskeleton.bootstrap.autoconfigure.outbox.MessagingAuthorityCutoverApplicationRunner
|
||||
OutboxEventJpaRepository.deletePublishedBefore
|
||||
OutboxEventJpaRepository.countGroupedByStatus
|
||||
OutboxEventJpaRepository.findOldestUnpublishedOccurredAtByEventType
|
||||
|
||||
@@ -162,7 +162,7 @@ into the new test package, rewritten to run through the single auto-configuratio
|
||||
`@Bean @ConditionalOnMissingBean(Clock.class) Clock httpClientClock()`. Once the whole capability is
|
||||
gated, that bean would vanish whenever HTTP Client is off — and Redis, idempotency and the Fileserver
|
||||
all inject `Clock`. The application context is unaffected because
|
||||
`dev.caskeleton.bootstrap.idempotency.IdempotencyConfig#systemClock` declares one unconditionally in
|
||||
`dev.caskeleton.bootstrap.autoconfigure.idempotency.IdempotencyConfig#systemClock` declares one unconditionally in
|
||||
a scanned package, so the httpclient copy is redundant *in the application* and dangerous *in the
|
||||
gate*. Isolated `ApplicationContextRunner` tests must supply their own, exactly as
|
||||
`FileserverPlatformAutoConfigurationTest` supplies a `MeterRegistry`.
|
||||
|
||||
@@ -382,7 +382,7 @@ public final class AdapterActivationInventory {
|
||||
NOTIFICATION(
|
||||
Set.of(
|
||||
"dev.caskeleton.adapter.outbound.notification",
|
||||
"dev.caskeleton.bootstrap.notification"),
|
||||
"dev.caskeleton.bootstrap.autoconfigure.notification"),
|
||||
Set.of(),
|
||||
"notification-"),
|
||||
GRAPHQL(
|
||||
|
||||
@@ -668,7 +668,7 @@ class CompositionScanNarrownessTest {
|
||||
Pattern excluded = Pattern.compile(regexExcludeOf(APPLICATION.getAnnotation(ComponentScan.class).excludeFilters()));
|
||||
for (String type :
|
||||
new String[] {
|
||||
"dev.caskeleton.bootstrap.autoconfigure.persistencejpa.PersistenceJpaRootAutoConfiguration",
|
||||
"dev.caskeleton.bootstrap.autoconfigure.jpa.PersistenceJpaRootAutoConfiguration",
|
||||
"dev.caskeleton.adapter.outbound.mongo.MongoPersistenceConfig",
|
||||
"dev.caskeleton.adapter.outbound.messaging.MessagingSettings",
|
||||
"dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings",
|
||||
@@ -800,7 +800,7 @@ design:
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```java
|
||||
package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
|
||||
package dev.caskeleton.bootstrap.autoconfigure.jpa;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -871,7 +871,7 @@ class JpaMasterGateTest {
|
||||
- [ ] **Step 3: Write `DataSourceRequirement`**
|
||||
|
||||
```java
|
||||
package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
|
||||
package dev.caskeleton.bootstrap.autoconfigure.jpa;
|
||||
|
||||
import dev.caskeleton.shared.activation.MasterSwitch;
|
||||
import java.util.ArrayList;
|
||||
@@ -943,7 +943,7 @@ public final class DataSourceRequirement {
|
||||
- [ ] **Step 4: Write the off-filter**
|
||||
|
||||
```java
|
||||
package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
|
||||
package dev.caskeleton.bootstrap.autoconfigure.jpa;
|
||||
|
||||
import java.util.Set;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
|
||||
@@ -1008,7 +1008,7 @@ public final class JpaOffAutoConfigurationImportFilter
|
||||
Create `PersistenceJpaRootAutoConfiguration`:
|
||||
|
||||
```java
|
||||
package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
|
||||
package dev.caskeleton.bootstrap.autoconfigure.jpa;
|
||||
|
||||
import dev.caskeleton.bootstrap.autoconfigure.jpa.JpaPlatformRuntimeAutoConfiguration;
|
||||
import dev.caskeleton.bootstrap.autoconfigure.jpa.JpaSafetySettings;
|
||||
@@ -1048,9 +1048,9 @@ Then:
|
||||
owns that import — and keep the vendor-specific beans.
|
||||
- Remove `dev.caskeleton.bootstrap.autoconfigure.jpa.JpaPlatformRuntimeAutoConfiguration` from
|
||||
`AutoConfiguration.imports` and add
|
||||
`dev.caskeleton.bootstrap.autoconfigure.persistencejpa.PersistenceJpaRootAutoConfiguration`.
|
||||
`dev.caskeleton.bootstrap.autoconfigure.jpa.PersistenceJpaRootAutoConfiguration`.
|
||||
- Create `AutoConfigurationImportFilter.imports` containing
|
||||
`dev.caskeleton.bootstrap.autoconfigure.persistencejpa.JpaOffAutoConfigurationImportFilter`.
|
||||
`dev.caskeleton.bootstrap.autoconfigure.jpa.JpaOffAutoConfigurationImportFilter`.
|
||||
|
||||
- [ ] **Step 6: Run the test to verify it passes.**
|
||||
Run: `cd src && ./gradlew :app-bootstrap:test --tests '*JpaMasterGateTest*' --console=plain --no-daemon`
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
# CI 단계 분리 + 컨테이너 릴리스 도입
|
||||
|
||||
- 작성: 2026-09-16
|
||||
- 상태: Track A 완료(커밋 3개) / Track B 진행 중 / Track C 대기
|
||||
- 근거 감사: 빌드·CI 레이어 전수 리뷰 133건 (파일 110개 / 12,800줄)
|
||||
|
||||
## 확정된 결정
|
||||
|
||||
1. **배포 단위는 `app-bootstrap` 하나.** 어댑터는 독립 배포되지 않는다.
|
||||
따라서 release 워크플로는 8개가 아니라 1개다. 어댑터별로 필요한 것은
|
||||
release가 아니라 PR 단계의 선택적 테스트다.
|
||||
2. **GitOps 매니페스트는 별도 repo.** 단, 이번 작업 범위 밖이다.
|
||||
이 repo는 "이미지를 만들고 태그를 확정"하는 데서 끝난다.
|
||||
ArgoCD Application 정의와 매니페스트는 이미지가 생긴 뒤 착수한다.
|
||||
3. **작업은 main에서 직접 하고 커밋한다** (사용자 지시).
|
||||
`CLAUDE.md:36` 의 `commit policy is human-only` 와 충돌하므로
|
||||
그 줄도 이번에 함께 갱신한다. push 는 하지 않는다.
|
||||
|
||||
## 현재 구조의 문제 — 한 줄
|
||||
|
||||
**워크플로가 단계가 아니라 모듈로 쪼개져 있다.**
|
||||
|
||||
28개 워크플로가 전부 "어느 모듈이냐"(`web-*`, `jpa-*`, `httpclient-*` …)로 갈렸고
|
||||
"어느 단계냐"로는 갈리지 않았다. 결과:
|
||||
|
||||
- 같은 성격의 일이 9개 파일에 흩어진다
|
||||
- 한 파일 안에 PR 검증과 릴리스 게이트가 섞인다
|
||||
- 동일한 13줄 setup 블록이 35회 복붙됐다 (CI 366줄)
|
||||
- 어떤 게이트가 개발을 막고 어떤 게이트가 안 막는지 파일만 봐서는 모른다
|
||||
|
||||
이 상태에서는 "이 게이트가 쓸모 있나"를 물을 수 없다. 단계가 하나뿐이면
|
||||
모든 게이트가 똑같이 개발을 막기 때문이다.
|
||||
|
||||
## 목표 구조
|
||||
|
||||
| 단계 | 답하는 질문 | 예산 | 트리거 | 실패 시 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 PR 게이트 | 이 diff가 안전한가 | 5분 | PR, 변경 모듈만 | 머지 차단 |
|
||||
| 2 통합 | 합쳐진 상태가 건강한가 | 30분 | main push | 알림, 머지는 이미 끝남 |
|
||||
| 3 릴리스 | 배포 가능한 산출물 생성 | — | tag | 릴리스 중단 |
|
||||
| 4 CD | 클러스터를 산출물로 수렴 | — | ArgoCD 폴링 | (이번 범위 밖) |
|
||||
|
||||
### 게이트 재배치 원칙
|
||||
|
||||
감사에서 나온 B등급 36건(어겨도 프로그램은 도는 문서·네이밍·개수 검증)은
|
||||
**삭제 여부를 논쟁하지 않는다. 단계를 지정한다.**
|
||||
|
||||
- 1단계: 컴파일, 의존성 방향, 잠금파일, 시크릿·취약점 스캔, 변경 모듈 테스트
|
||||
- 2단계: 문서-코드 일치, 공개 경로 스냅샷, env 키, 전체 테스트, 느린 통합 테스트
|
||||
- 3단계: 이미지 빌드, SBOM, 서명, 릴리스 차단 게이트 집계
|
||||
|
||||
1단계에 있으면 개발을 막고, 2단계에 있으면 안 막는다. 이 배치가
|
||||
"쓸모 있나"라는 질문을 대체한다.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
### Track A — 깨진 게이트 (진행 중)
|
||||
|
||||
설계 논쟁이 필요 없는 E등급 24건. 구조는 건드리지 않는다.
|
||||
|
||||
핵심: **아키텍처 게이트가 규칙을 0개 실행하고 있다.**
|
||||
워크플로 7곳과 `CLAUDE.md:109` 가 `--tests '*CleanArchitectureTest'` 를 지정하는데
|
||||
그 이름의 클래스는 존재하지 않는다. 실재하는 ArchUnit 테스트는 17개
|
||||
(`AdapterBoundaryArchitectureTest`, `DomainPurityArchitectureTest` 등, ArchUnit 사용 37개 클래스).
|
||||
4곳은 하드 실패하고, 3곳은 다른 필터와 병기돼 아키텍처 규칙 0개를 돌고 초록으로 통과한다.
|
||||
|
||||
그 외: `release_blocking` 미강제(trivy-fs 가 빨개도 release-gate 초록),
|
||||
`strict-test-lane` 이 skip 을 실행으로 셈, `public-path-snapshot` 이 gitignore 된 `src/.env` 를 읽음,
|
||||
`verifyEnvKeys` 가 build 산출물을 소스로 읽음, `jpa-next-*` 3개가 실제 테스트 없이 continue-on-error,
|
||||
`spring70CompatibilityTest` fail-closed 상실, `fileserver-pr` 존재하지 않는 path 필터.
|
||||
|
||||
### Track B — 단계 분리
|
||||
|
||||
1. 재사용 워크플로(`workflow_call`) + composite action 으로 setup 블록 공통화
|
||||
2. release 워크플로 8개 → 1개. 릴리스 태그 네임스페이스 분열 버그도 여기서 해소
|
||||
3. PR 단계는 경로 필터로 변경 모듈만 실행
|
||||
4. 게이트를 위 표대로 재배치. 문서 검증류는 2단계로 내린다
|
||||
5. `ci-gate-matrix.yml` 이 실제로 release 차단을 강제하도록 연결
|
||||
(이 파일은 죽은 문서가 아니다 — `ci-quality-gates.yml:88` 이 런타임 파싱한다)
|
||||
6. 도달 불가 Gradle 태스크 39개 정리 (grpc 워크플로가 0개인 것이 주원인)
|
||||
|
||||
### Track C — 컨테이너 릴리스 (축소된 범위)
|
||||
|
||||
지금 없는 것: 이미지 빌드·푸시. `src/Dockerfile` 은 있으나
|
||||
`build-push-action` / `bootBuildImage` / `jib` 사용처가 0건이다.
|
||||
`*-release.yml` 8개는 테스트 실행 + evidence 업로드로 끝난다 — 이름만 릴리스다.
|
||||
|
||||
1. 3단계 릴리스 워크플로에 이미지 빌드 + 레지스트리 푸시 추가 (기본 ghcr.io)
|
||||
2. 태그 규칙 확정 (semver + git sha)
|
||||
3. SBOM 생성, 이미지 스캔
|
||||
4. **CI 는 배포하지 않는다.** `kubectl apply` 를 CI 에 넣지 않는다 — GitOps 원칙.
|
||||
현재 그런 코드가 없으므로 걷어낼 것도 없다.
|
||||
|
||||
이후(별도 작업): GitOps repo, 매니페스트, ArgoCD Application, image tag bump 연결.
|
||||
|
||||
## 검증
|
||||
|
||||
- Track A 완료 시: 변경한 YAML 전수 파싱, 아키텍처 테스트가 실제로 실행되는지 확인
|
||||
- Track B 완료 시: `verifyCleanArchitectureDependencies`, 아키텍처 테스트, 워크플로 파싱
|
||||
- 각 Track 종료 시 무엇을 실행했고 무엇을 실행하지 못했는지 명시한다.
|
||||
실행하지 못한 검증은 "실행하지 않음"이라고 적는다.
|
||||
|
||||
## 감사 산출물
|
||||
|
||||
- 루브릭: `scratchpad/gradle-audit/RUBRIC.md`
|
||||
- finding 전체(243KB, `file:line` 근거): `scratchpad/gradle-audit/findings/R1~R8.md`
|
||||
- 등급 분포: A=25 B=36 C=17 D=31 E=24 · 정리 시 3,368줄 감소 추정
|
||||
- 아키텍처 위반 0건 (`modules.json` 전수 대조, messaging/grpc 격리 확인)
|
||||
|
||||
|
||||
## 진행 기록
|
||||
|
||||
### Track A — 완료 (2026-09-16)
|
||||
|
||||
커밋 `2a8d34f` docs / `e345191` fix(ci) / `1535481` refactor(build,src).
|
||||
E등급 19건 처리. 깨끗한 worktree 체크아웃에서 검증:
|
||||
`./gradlew help` 통과, 아키텍처 게이트 20개 클래스 174 tests 실패 0 스킵 0,
|
||||
`-p build-logic test` 통과, `verify-gradle-wrapper.sh` PASS,
|
||||
`verify-gate-matrix.sh` OK.
|
||||
|
||||
감사 범위 밖이었으나 main 이 실제로 깨져 있던 것 두 건도 함께 고쳤다:
|
||||
`src/gradle/libs.versions.toml` 와 `app-bootstrap config/*.yml` 15개가
|
||||
git 에 없어 깨끗한 체크아웃에서 빌드가 불가능했다.
|
||||
|
||||
**절차 교훈**: 커밋을 4번 시도해 전부 되돌렸다. 원인은 깨끗한 체크아웃 검증을
|
||||
커밋 *후* 에 한 것. 이후로는 워킹트리를 커밋하지 않고 후보 커밋 객체로 만들어
|
||||
새 체크아웃에서 검증한 뒤에만 커밋한다.
|
||||
|
||||
### 감사 findings 중 서브에이전트가 반박해 기각한 것
|
||||
|
||||
리뷰 결과를 그대로 집행하지 않는다. 수정 담당이 근거를 들어 반박한 건은 기각한다.
|
||||
|
||||
- `runtimeClasspathManifest` "출력 미사용" — 거짓. `RuntimeMembershipClasspathAgreementTest` 가
|
||||
읽고 `app-bootstrap/build.gradle:415` 에 `test dependsOn` 이 걸려 있다
|
||||
- JUnit 태그 3중 재설정 "충돌" — Gradle 9.0.0 에서 `useJUnitPlatform{}` 은 누적된다(실측)
|
||||
- `persistence-jpa:226` outbound→inbound — 클래스패스가 아닌 태스크 엣지라 게이트 범위 밖
|
||||
- `ca.api-surface` "소비자 없음" — 거짓. `ci-gate-matrix.yml:90-100` 과
|
||||
`verify-gate-matrix.sh` 가 소비한다. 유지하고 정규식 렌더러만 javac 파싱으로 교체
|
||||
- `ca.dependency-policy` "의존성 잠금이 대체한다" — 거짓. 락파일은 무엇이 있는지를
|
||||
기록할 뿐 무엇이 없어야 하는지를 막지 않고, `--write-locks` 는 추가를 조용히 수용한다
|
||||
- `ca.runtime-membership` 의 `moduleRegistryRepositoryRoot` "미사용" — 거짓.
|
||||
`RuntimeMembershipFunctionalTest.java:123` 이 쓴다
|
||||
|
||||
|
||||
### 아키텍처 게이트가 실제로 뭘 검사하는지 측정 (2026-09-16)
|
||||
|
||||
`allowEmptyShould(true)` 가 96곳에 있어 "규칙이 클래스 0개를 검사하고 초록으로
|
||||
통과하는 것 아니냐"를 의심했다. 추측 대신 측정했다 — 스크래치 worktree 에서
|
||||
96곳을 전부 `false` 로 뒤집고 아키텍처 스위트를 돌렸다.
|
||||
|
||||
**174개 중 3개만 실패했다.** 93곳의 억제는 실제로 클래스를 검사하는 규칙에
|
||||
방어적으로 붙어 있었다. 비어 있는 3개는 전부 "이 구조를 추가하면 이 규칙을
|
||||
지켜라" 형태의 선행 가드이고, 템플릿이라 아직 해당 구조가 없다:
|
||||
|
||||
- `AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC` — `set*` 를 가진 `@AggregateRoot` 없음
|
||||
- 테넌트 스코프 리포지토리 없음
|
||||
- `CrudRepository` 를 재구현한 프로덕션 타입 없음
|
||||
|
||||
결론: 아키텍처 게이트는 속 빈 게이트가 아니다. 의심이 틀렸다.
|
||||
|
||||
### HEAD 에서 발견된 실제 실패 2건 (감사 findings 밖)
|
||||
|
||||
1. `MessagingCapabilityRegistryContractTest` — `src/build.gradle` 의 **소스 문자열
|
||||
6개**를 assert 했다. 결과에 영향 없던 검증 45줄을 지우자 구현이 아니라 테스트가
|
||||
먼저 깨졌다. 계약 검사로 교체: 모든 스켈레톤이 공용 가드를 통과하는지와,
|
||||
그 가드가 실제로 throw 하는지만 본다.
|
||||
2. `MongoModuleBoundaryTest` — `DO_NOT_INCLUDE_JARS` 때문에 임포트가 0개가 되어
|
||||
규칙 10개 전부가 "failed to check any classes" 로 실패하고 있었다. 이 레인에서는
|
||||
모듈 자기 클래스가 jar 로 클래스패스에 올라온다. 옵션 제거로 해결(실험으로 확인).
|
||||
`importPackages(ROOT)` 가 이미 서드파티를 걸러내므로 옵션은 불필요했다.
|
||||
|
||||
## 최종 결과와, 감사 추정이 틀린 부분 (2026-09-16)
|
||||
|
||||
감사는 "정리하면 3,368줄 감소"라고 추정했다. **실제로는 빌드·CI 레이어가
|
||||
1,652줄 늘었다.** 추정이 틀렸고, 그 이유를 적어둔다.
|
||||
|
||||
| 영역 | 증감 | 이유 |
|
||||
| --- | --- | --- |
|
||||
| `.github/workflows` | −128줄 (파일 30 → 19) | 중복 제거는 실제로 됐다. setup 블록 59회 → composite 1개, 잡 8개 삭제 |
|
||||
| `.github/ci-gate-matrix.yml` | **+634줄** | 커버리지 8/28 → 28/28 워크플로. 게이트 49 → 107행 |
|
||||
| `.github/scripts` | +163줄 | 개수 고정 대신 불변식 검증, 워크플로 SHA 잠금 확대 |
|
||||
| `.github/actions` | +37줄 | composite action 신설 |
|
||||
| `src/build.gradle` | +276줄 | Checkstyle 배선 + 판단 근거 주석 |
|
||||
| `src/gradle` | −70줄 | jpa-evidence −274 를 다른 증가가 상쇄 |
|
||||
| `src/build-logic` | **+701줄** | convention 플러그인 2개·헬퍼 2개 신설(+252) 과 그 테스트(+424) |
|
||||
| 모듈 `build.gradle` | +3줄 | convention 적용으로 줄었지만 레인 전환이 상쇄 |
|
||||
|
||||
**왜 추정이 빗나갔나**
|
||||
|
||||
1. **감사 findings 17건이 근거와 함께 기각됐다.** 지울 수 있다고 본 코드가
|
||||
실제로는 소비자가 있었다. 감사는 읽고 판단했고, 수정 담당은 실행해서
|
||||
확인했다. 실행이 이겼다.
|
||||
2. **작동하게 만드는 데는 코드가 든다.** 게이트 매트릭스가 워크플로 8개만
|
||||
덮고 있던 것을 28개 전부로 넓히면 634줄이 는다. 그 634줄이 "군더더기"가
|
||||
아니라 그동안 비어 있던 자리다.
|
||||
3. **convention 플러그인은 총량이 아니라 중복을 줄인다.** build-logic 이
|
||||
701줄 늘고 모듈 43개가 그만큼 줄지 않은 것은, 플러그인에 테스트(424줄)를
|
||||
붙였기 때문이다. 테스트 없는 convention 플러그인은 43개 모듈에 동시에
|
||||
틀릴 수 있는 한 지점이 된다.
|
||||
|
||||
**그래서 이 작업의 성과는 "코드가 줄었다"가 아니다.** 안 돌던 게이트가 돌고,
|
||||
CI 에 단계가 생기고, 깨끗한 체크아웃에서 빌드가 되는 것이다. 줄 수로 보고하면
|
||||
틀린 보고가 된다.
|
||||
|
||||
### 최종 커밋
|
||||
|
||||
`2a8d34f` docs · `e345191` CI 게이트 정상화 · `1535481` testkit 이관 + 미추적
|
||||
필수 파일 · `9bc2e75` CI 단계 분리 + 잔여 114건 · `40ee9f1` 고아 레인 배선 +
|
||||
카탈로그 이관.
|
||||
|
||||
모든 커밋은 워킹트리를 커밋하지 않고 후보 커밋 객체로 만들어 깨끗한 체크아웃에서
|
||||
검증한 뒤에 올렸고, 커밋된 트리 해시가 검증한 트리와 같은지 매번 대조했다.
|
||||
@@ -113,7 +113,7 @@ dev.caskeleton.adapter.outbound.notification.core.RoutingNotifier
|
||||
dev.caskeleton.adapter.outbound.notification.email.google.GoogleEmailNotificationAdapterConfig
|
||||
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings
|
||||
dev.caskeleton.adapter.outbound.notification.slack.webhook.SlackNotificationAdapterConfig
|
||||
dev.caskeleton.bootstrap.notification.NotificationPlatformSecretsConfig$NotificationSecretsSettings
|
||||
dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformSecretsConfig$NotificationSecretsSettings
|
||||
```
|
||||
|
||||
## Task 5 — default-profile boot
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# 검증 표면 축소 설계 — 스켈레톤을 qualification framework에서 되돌리기
|
||||
|
||||
날짜: 2026-09-16
|
||||
근거: 외부 리뷰 "현재 상태를 유지하기 위한 검증이 너무 많고, 그 검증 자체를 다시 검증하는 구조까지 생겼다"
|
||||
|
||||
## 0. 리뷰 기준점과 현재 체크아웃의 차이
|
||||
|
||||
리뷰는 이 저장소의 **이전 스냅샷**을 보고 작성됐다. 실제 작업 전에 항목별로 재측정했고,
|
||||
이미 해결된 항목은 "완료"로 확정하고 남은 항목만 작업 대상으로 삼는다.
|
||||
|
||||
| 리뷰 주장 | 리뷰가 본 값 | 현재 실측 | 판정 |
|
||||
| --- | --- | --- | --- |
|
||||
| `settings.gradle` 183줄 validator | 183줄 | 16줄 (`ca.architecture-registry` 설정 플러그인으로 이전) | 완료 |
|
||||
| 모듈 수 정확히 18개 강제 | 있음 | 없음 | 완료 |
|
||||
| runtime composition이 정확히 `app-bootstrap` | 있음 | `runtime_compositions`를 JSON에서 읽음 | 완료 |
|
||||
| JSON 필드 집합 정확히 일치 | 있음 | `ModuleRegistry.groovy:88,124`에 그대로 있음 | **작업 대상** |
|
||||
| `sample-portfolio` negative re-entry guard | 있음 | `ModuleRegistry.groovy:215`에 그대로 있음 | **작업 대상** |
|
||||
| `build-logic` 없음 | 없음 | 존재 (9개 convention plugin) | 부분 완료 |
|
||||
| version catalog 없음 | 없음 | `gradle/libs.versions.toml` 140줄 | 완료 |
|
||||
| `adapter/inbound/web/build.gradle` 799줄 OpenAPI | 799줄 | 256줄, codegen 없음 | 완료 |
|
||||
| leaf `check`가 저장소 전체 검사 | 그랬음 | 루트 `check`로 이미 이전 | 부분 완료 |
|
||||
| `fileserver-release.yml`의 `kubectl apply` | 있음 | 이미 제거됨 | 완료 |
|
||||
| `httpclient-release.yml` | 있음 | 파일 자체가 없음 | 해당 없음 |
|
||||
| `ci-gate-matrix.yml` 282줄 / 37 gate | 282줄 | **1,025줄 / 107 gate** | **작업 대상(악화)** |
|
||||
| `verify-gate-matrix.sh` | 있음 | 568줄 | **작업 대상** |
|
||||
| `verify-gradle-wrapper.sh` 740줄 | 740줄 | **799줄** | **작업 대상** |
|
||||
| `DeveloperExperienceContractTest` 1,100줄 | 1,100줄 | 1,141줄 (CI YAML mutation test 25개) | **작업 대상** |
|
||||
| `src/build.gradle` 2,469줄 | 2,469줄 | **3,211줄** | **작업 대상(악화)** |
|
||||
| always-fail Messaging task | 있음 | 9개 그대로 | **작업 대상** |
|
||||
| 모든 빌드에 Git SHA 강제 | 있음 | 그대로 (`build.gradle:47`) | **작업 대상** |
|
||||
|
||||
## 1. 채택하는 판단 기준
|
||||
|
||||
리뷰의 핵심 원칙을 이 저장소의 결정 규칙으로 승격한다.
|
||||
|
||||
1. **현재 상태(Current State)가 아니라 불변조건(Invariant)을 검증한다.**
|
||||
"모듈이 N개다", "필드가 정확히 이 집합이다", "문서에 적힌 수가 레지스트리와 같다"는 현재 상태다.
|
||||
"ID가 중복되지 않는다", "domain이 framework를 참조하지 않는다"는 불변조건이다.
|
||||
2. **검증기를 검증하지 않는다.** validator를 mutation해서 validator가 실패하는지 보는 task는
|
||||
스켈레톤의 기본 빌드 정책이 아니다.
|
||||
3. **자동으로 구성할 수 있는 것은 검증으로 강제하지 않는다.** convention plugin으로 주입한다.
|
||||
4. **로컬 `check`는 로컬이어야 한다.** leaf의 `check`는 그 leaf만 검사한다.
|
||||
5. **릴리스 불변조건을 일반 개발 빌드에 강제하지 않는다.**
|
||||
6. **문서 drift는 빌드 실패 사유가 아니다.** 커스텀 Markdown/Java 파서를 유지하지 않는다.
|
||||
7. **GitHub Actions = CI + artifact 생산, Argo CD = CD.** CI에 클러스터 배포 자격증명을 넣지 않는다.
|
||||
8. **Template maintainer용 검증과 Template consumer용 검증을 분리한다.**
|
||||
|
||||
이 기준은 기존의 D8 결정("quality 블록을 convention plugin으로 빼지 않는다")을 **대체한다**.
|
||||
D8의 3번 근거(build-logic이 플러그인 버전을 두 번 선언하게 된다)는 이미 무효다 —
|
||||
`build-logic/settings.gradle`이 메인 빌드의 `libs.versions.toml`을 읽고 있으므로 버전은 한 곳에 있다.
|
||||
|
||||
## 2. 목표 task 계층
|
||||
|
||||
```text
|
||||
:<leaf>:check 컴파일 + 단위 테스트 + spotless + checkstyle + errorprone (그 leaf만)
|
||||
check (root) 모든 leaf의 check
|
||||
architectureCheck 의존 방향 · 런타임 멤버십 · application-core 순수성 · Git 미추적 패키지
|
||||
qualityCheck SpotBugs + FindSecBugs (전 leaf)
|
||||
configContractCheck :app-bootstrap:verifyEnvKeys
|
||||
integrationCheck 통합/슬라이스 레인
|
||||
ci check + architectureCheck + qualityCheck + configContractCheck
|
||||
releaseCheck ci + 아카이브 위생 + public path snapshot + 릴리스 provenance
|
||||
```
|
||||
|
||||
qualification(JPA readiness, Messaging evidence, notification evidence, transport 등)은
|
||||
어느 것도 `check` / `ci`에 걸지 않는다. 명시적으로 이름을 불러야 실행된다.
|
||||
|
||||
## 3. 변경 목록
|
||||
|
||||
### 3.1 삭제
|
||||
|
||||
| 대상 | 줄 수 | 이유 |
|
||||
| --- | ---: | --- |
|
||||
| `.github/ci-gate-matrix.yml` | 1,025 | Gradle task graph와 workflow graph에 이미 있는 정보의 3중 복제 |
|
||||
| `.github/scripts/verify-gate-matrix.sh` | 568 | 위 복제본의 정합성 검사기 |
|
||||
| `.github/scripts/verify-gradle-wrapper.sh` | 799 | workflow 바이트 해시 잠금. 공격자는 해시도 같이 고치면 되고, 개발자는 주석 하나에 해시를 갱신해야 한다 |
|
||||
| `DeveloperExperienceContractTest`의 wrapper/gate mutation test 25개 | ~700 | 애플리케이션 test suite가 GitHub Actions YAML 파서를 검증 |
|
||||
| always-fail Messaging skeleton task 9개 | ~85 | 정상 입력으로도 성공할 수 없는 task. TODO를 Gradle API로 만든 것 |
|
||||
| `verifyReadmeCommands` | 105 | 커스텀 Markdown 명령 파서 |
|
||||
| `verifyRunbookReferences` | 75 | 커스텀 runbook 식별자 파서 |
|
||||
| `verifyDocumentedLeafCount` | 78 | 문서에 적힌 leaf 수 = 전형적인 현재 상태 검증 |
|
||||
| `verifyTestSourceSetRegistry` | 92 | 문서 표 ↔ source set 대조 파서 |
|
||||
| `verifySpotBugsAnalysisFailureContract` | 58 | 검증기의 검증 |
|
||||
| `verifyConfigurationPropertiesProcessor` | 90 | build.gradle을 regex로 읽는 검증 → convention으로 대체 |
|
||||
| `verifyOneTypePerFile` | 8 | 이미 `checkstyleMain` 별칭. 호출자를 `checkstyleMain`으로 바꾸고 이름 폐기 |
|
||||
| `verifyTrivyignore` | 105 | 빈 registry를 지키는 커스텀 YAML 파서 |
|
||||
| `verifyQuarantineSunset` | 250 | 빈 registry를 지키는 커스텀 YAML + Java 파서 |
|
||||
| `blankJavaCommentsAndLiterals` | 95 | 위 두 개만 쓰던 Java 렉서 흉내 |
|
||||
| `ModuleRegistry`의 필드 집합 정확 일치 · sample-portfolio negative guard | ~25 | 확장 차단 · 삭제된 모듈의 역사가 영구 invariant |
|
||||
|
||||
합계 약 4,150줄.
|
||||
|
||||
### 3.2 이동
|
||||
|
||||
| 대상 | 현 위치 | 새 위치 | 이유 |
|
||||
| --- | --- | --- | --- |
|
||||
| java/quality/spring 공통 설정 | `build.gradle`의 `configure(subprojects…)` | `ca.java-conventions` · `ca.quality-conventions` · `ca.java-library` · `ca.spring-library` | 모듈이 자신의 성격을 스스로 선언 |
|
||||
| `verifyCleanArchitectureDependencies` 외 3개 | `build.gradle` | `ca.architecture` | 아키텍처 규칙을 한 곳에 |
|
||||
| JPA readiness registry + release gate | `build.gradle` ~610줄 | `gradle/qualification/jpa-qualification.gradle` | 빌드 정책과 certification 분리 |
|
||||
| Messaging evidence manifest | `build.gradle` ~600줄 | `gradle/qualification/messaging-qualification.gradle` | 동일 |
|
||||
| `verifyEnvKeys` | 루트 task, 루트 `check` | `:app-bootstrap` 소유, `configContractCheck` | 환경 계약은 composition root의 책임 |
|
||||
| 모듈 의존 edge 존재/자기참조 검사 | settings 단계(`ModuleRegistry`) | `verifyCleanArchitectureDependencies` | settings에서 죽으면 복구 수단이 없다 |
|
||||
|
||||
### 3.3 완화
|
||||
|
||||
| 대상 | 현재 | 변경 후 |
|
||||
| --- | --- | --- |
|
||||
| Git revision | 없으면 **모든** 빌드가 configuration 단계에서 실패 | 일반 빌드는 `0.0.1-SNAPSHOT`/`unknown`. `releaseCheck`·아카이브 생성에서만 요구 |
|
||||
| SpotBugs / FindSecBugs | 전 leaf `check` 블로킹 | `qualityCheck` (CI lane). 로컬 `check`에서 제외 |
|
||||
| `.trivyignore.yaml` | 커스텀 파서가 expiry/reason 강제 | 파일은 유지, 규칙은 문서화 + CODEOWNERS 승인 |
|
||||
| `flaky-quarantine.yaml` | 커스텀 파서 + 14일 sunset 강제 | 레지스트리 삭제. `@Tag("quarantine")` 제외와 `quarantineTest`는 유지(각 3줄) |
|
||||
|
||||
### 3.4 CI
|
||||
|
||||
```text
|
||||
.github/workflows/
|
||||
├── _reusable-gradle.yml 신규 — checkout + wrapper validation + JDK/캐시 + Gradle 호출
|
||||
├── ci-quality-gates.yml → 재사용 workflow 호출로 축약
|
||||
├── dependency-vulnerability.yml 유지 (dependency-review + submission + Trivy)
|
||||
├── link-check.yml 유지
|
||||
├── release.yml image/SBOM 생산까지. 클러스터 배포 없음
|
||||
├── fileserver-certification.yml ← fileserver-release.yml 개명 (CD가 아니라 certification)
|
||||
└── 나머지 feature qualification 유지, 전부 재사용 workflow 사용
|
||||
```
|
||||
|
||||
wrapper 검증은 `gradle/actions/wrapper-validation`(full SHA 핀)에 맡기고, 재사용 workflow
|
||||
한 곳에서만 선언한다. full SHA 핀은 리뷰 판단대로 **유지**한다.
|
||||
|
||||
### 3.5 CI/CD 경계
|
||||
|
||||
```text
|
||||
GitHub Actions ──► test / scan / image build / SBOM / push ──► GitOps repo manifest ──► Argo CD ──► K8s
|
||||
```
|
||||
|
||||
`docs/ci-cd/boundary.md`로 고정한다. GitHub Actions는 `kubectl apply` / `helm upgrade` /
|
||||
`argocd app sync`를 하지 않는다. Argo CD auto-sync를 쓰면 CI에 클러스터 자격증명이 필요 없다.
|
||||
|
||||
### 3.6 Template maintainer vs consumer
|
||||
|
||||
`docs/ci-cd/template-vs-consumer.md`로 구분을 명시한다.
|
||||
|
||||
- Template CI: sample 모듈 제거 가능성, optional 모듈 조합 빌드, 레지스트리 확장 가능성
|
||||
- Consumer CI: 자기 애플리케이션의 test / architecture / security / release
|
||||
|
||||
파생 프로젝트가 가져가면 안 되는 workflow와 task를 목록으로 적는다.
|
||||
|
||||
## 4. 유지하는 것 (리뷰가 "잘한 것"으로 분류)
|
||||
|
||||
`verifyCleanArchitectureDependencies`, dependency locking(STRICT), full SHA action 핀,
|
||||
dependency-review, Trivy 스캔, path filter 기반 feature CI, nightly 분리, Spotless,
|
||||
`-Werror`/`-Xlint`, ErrorProne, 재현 가능한 아카이브.
|
||||
|
||||
## 5. 검증 방법
|
||||
|
||||
- `./gradlew help --offline`로 configuration 성공
|
||||
- 변경한 leaf마다 `./gradlew <path>:check --offline`
|
||||
- `./gradlew architectureCheck --offline`
|
||||
- 워크플로 YAML은 `python3 -c "import yaml…"`로 파싱 확인
|
||||
- 삭제한 task 이름이 저장소 어디에도 남지 않았는지 `grep`
|
||||
|
||||
## 6. 명시적 위험
|
||||
|
||||
1. leaf 62개에 `plugins {}` 블록을 추가한다. 적용 순서가 바뀌므로 leaf별 `check`로 확인한다.
|
||||
2. dependency locking이 STRICT라, 어떤 leaf의 configuration에 의존성이 추가되면 락 파일이 깨진다.
|
||||
따라서 convention 이동은 **해석되는 의존성 집합을 바꾸지 않는 범위**로 제한한다.
|
||||
`ca.spring-config`는 이미 processor를 선언한 leaf만 opt-in한다.
|
||||
3. 삭제하는 task 이름을 참조하는 workflow/문서/테스트를 같은 변경에서 고친다.
|
||||
@@ -0,0 +1,179 @@
|
||||
# 테스트 전략 — 레벨 정의와 소스셋 매핑 (SSOT)
|
||||
|
||||
- 기준 일자: 2026-09-07
|
||||
- 상태: **활성 문서.** 아래 §3 표는 사람이 유지한다. `verifyTestSourceSetRegistry` 가 이 문서의 §3 표와 실제 Gradle 소스셋 선언의
|
||||
불일치를 빌드 실패로 만든다.
|
||||
- 근거 리뷰: `docs/reviews/2026-09-07-app-bootstrap-module-code-review.md` (BOOT-014, BOOT-015,
|
||||
BOOT-016)
|
||||
|
||||
## 1. 이 문서가 존재하는 이유
|
||||
|
||||
이 저장소는 이미 테스트 레벨 계약을 **기계로** 강제하고 있었다 —
|
||||
`TestTaxonomyArchitectureTest` 가 contract/architecture 트리의 Testcontainers 의존을 금지하고,
|
||||
slice 애노테이션 혼합을 막고, fixture 유출을 잡는다. 없던 것은 **사람이 읽을 수 있는 정의**였다.
|
||||
|
||||
그 결과 계약은 "패키지 이름"에만 걸려 있었고 "어느 소스셋이 컴파일하는가"에는 걸려 있지 않았다.
|
||||
Testcontainers 를 쓰는 통합 테스트 9개가 `app-bootstrap/src/test` 안에 있었고, `@Testcontainers` 5개
|
||||
중 가드가 있는 것은 하나뿐이었다. 즉 `./gradlew :app-bootstrap:test` — 이 저장소가 leaf 별 기본
|
||||
명령으로 권장하는 바로 그 명령 — 이 Docker 데몬을 요구했다 (BOOT-014).
|
||||
|
||||
그래서 규칙을 두 가지 방식으로 동시에 고정한다. 사람은 이 문서를 읽고, 빌드는 §3 표를 읽는다.
|
||||
|
||||
## 2. 레벨 정의
|
||||
|
||||
레벨은 **이름이 아니라 "어디까지 실제로 붙여서 검증하는가"**로 정의한다. `smoke`, `regression`,
|
||||
`acceptance` 같은 말은 범위가 아니라 목적이라 레벨이 될 수 없다 — 하나의 E2E 테스트가 동시에
|
||||
smoke 이고 regression 일 수 있다.
|
||||
|
||||
| 레벨 | 무엇을 검증 | 외부 시스템 | 소스셋 |
|
||||
| --- | --- | --- | --- |
|
||||
| **unit** | 클래스·함수·도메인 규칙 | 없음 | `src/test` |
|
||||
| **slice** | 프레임워크 한 계층 | 인메모리/모의 | `src/test` |
|
||||
| **contract** | 모듈 경계의 형태와 약속 | 없음 | `src/test` |
|
||||
| **architecture** | 코드 의존 관계, 테스트 분류 자체 | 없음 | `src/test` |
|
||||
| **integration** | 실제 인프라와의 연결 | 실제 DB/브로커/스토리지 | `src/integrationTest` 또는 leaf 전용 레인 |
|
||||
| **qualification** | 벤더·프로토콜·배포 형상 | 실제 벤더 런타임 | leaf 전용 레인 |
|
||||
| **build-qualification** | 빌드·조립 계약 자체 | 없음 (별도 클래스패스) | leaf 전용 레인 |
|
||||
| **performance** | 지연·처리량 | 실제에 가까움 | leaf 전용 레인 |
|
||||
|
||||
### 2.1 소스셋을 나누는 기준은 하나다
|
||||
|
||||
> **테스트 코드는 production 패키지 구조를 그대로 미러링한다. 별도 소스셋으로 분리하는 것은
|
||||
> 실행 환경·의존성·클래스패스가 달라지는 경우뿐이다.**
|
||||
|
||||
`unit/`, `service/`, `repository/`, `regression/` 같은 폴더는 만들지 않는다. 서로 다른 분류 축을
|
||||
한 디렉터리에 섞으면 `UserServiceTest` 가 어디에 속하는지 아무도 답할 수 없게 된다. 이 저장소의
|
||||
`src/test` 는 이미 production 패키지를 미러링하고 있으며 그 상태를 유지한다.
|
||||
|
||||
### 2.2 build-qualification 은 폴더 취향이 아니다
|
||||
|
||||
`app-bootstrap` 의 세 레인은 "테스트를 분류하려고" 나눈 것이 아니라 **하나의 소스셋으로 표현할 수
|
||||
없는 클래스패스 차이** 때문에 존재한다. 합치면 검증 자체가 성립하지 않는다.
|
||||
|
||||
- `sampleOffTest` — `src/test` 와 **같은 소스 파일**을 `sample-portfolio` 없는 클래스패스로 다시
|
||||
컴파일한다. "샘플을 지워도 템플릿이 성립하는가"의 증명이며, 같은 파일을 두 클래스패스로 컴파일하는
|
||||
것이 그 정의다.
|
||||
- `conditionalTransportTest` — GraphQL/gRPC/WebSocket 을 **테스트 전용으로만** 클래스패스에 올린다.
|
||||
이 의존을 `testImplementation` 으로 옮기면 "기본 클래스패스에는 없다"는 증명 대상 명제가 그 순간
|
||||
거짓이 된다.
|
||||
- `functionalTest` — Gradle TestKit 이 별도 Gradle 빌드를 띄운다.
|
||||
|
||||
## 3. 소스셋 레지스트리 (기계 검증 대상)
|
||||
|
||||
이 표를 읽어 실제 `sourceSets` 선언과 대조하던 `verifyTestSourceSetRegistry` 는 2026-09에 삭제했다
|
||||
(Markdown 표 파서였고, `<!-- registry:begin -->` 마커가 사라지면 계약이 산문으로 되돌아가는 것을
|
||||
막으려고 마커 존재 자체까지 검사했다). 레인을 추가하면 이 표도 같이 고친다. 아래 옛 설명은 표를
|
||||
어떻게 읽어야 하는지에 대한 기준으로 남긴다: 표에 없는 소스셋을
|
||||
추가하거나 표에 있는 소스셋을 지우면 빌드가 실패한다.
|
||||
|
||||
<!-- registry:begin -->
|
||||
| Gradle 경로 | 소스셋 | 레벨 |
|
||||
| --- | --- | --- |
|
||||
| `:adapter:inbound:graphql` | `testFixtures` | fixtures |
|
||||
| `:adapter:inbound:web` | `jettyCompatTest` | qualification |
|
||||
| `:adapter:inbound:web` | `nginxProxyTest` | qualification |
|
||||
| `:adapter:inbound:web` | `testFixtures` | fixtures |
|
||||
| `:adapter:inbound:web` | `webfluxContractTest` | qualification |
|
||||
| `:adapter:inbound:websocket` | `jettyWebSocketTest` | qualification |
|
||||
| `:adapter:inbound:websocket` | `nginxWebSocketTest` | qualification |
|
||||
| `:adapter:inbound:websocket` | `testFixtures` | fixtures |
|
||||
| `:adapter:outbound:httpclient` | `httpClientPerformanceTest` | performance |
|
||||
| `:adapter:outbound:httpclient` | `jmh` | performance |
|
||||
| `:adapter:outbound:httpclient` | `testFixtures` | fixtures |
|
||||
| `:adapter:outbound:objectstorage` | `objectStorageAwsQualificationTest` | qualification |
|
||||
| `:adapter:outbound:objectstorage` | `objectStorageMinioContractTest` | integration |
|
||||
| `:adapter:outbound:objectstorage` | `objectStorageMinioFaultTest` | integration |
|
||||
| `:adapter:outbound:persistence-jpa` | `jpaPlatformPerformanceTest` | performance |
|
||||
| `:adapter:outbound:persistence-jpa` | `postgresqlIntegrationTest` | integration |
|
||||
| `:adapter:outbound:persistence-jpa` | `testFixtures` | fixtures |
|
||||
| `:adapter:outbound:persistence-mongo` | `mongoPerformanceTest` | performance |
|
||||
| `:adapter:outbound:persistence-mongo` | `testFixtures` | fixtures |
|
||||
| `:app-bootstrap` | `conditionalTransportTest` | build-qualification |
|
||||
| `:app-bootstrap` | `functionalTest` | build-qualification |
|
||||
| `:app-bootstrap` | `integrationTest` | integration |
|
||||
| `:app-bootstrap` | `sampleOffTest` | build-qualification |
|
||||
| `:messaging:messaging-kafka` | `jmh` | performance |
|
||||
| `:messaging:messaging-rabbit` | `jmh` | performance |
|
||||
| `:messaging:messaging-testkit` | `jmh` | performance |
|
||||
| `:sample-portfolio` | `posterImageMigrationTest` | qualification |
|
||||
| `:shared-contract` | `edgeRateLimitContractTest` | contract |
|
||||
<!-- registry:end -->
|
||||
|
||||
`src/test` 는 모든 leaf 가 갖는 기본 소스셋이므로 표에 적지 않는다.
|
||||
|
||||
## 4. 판단표 — 새 테스트를 어디에 쓰는가
|
||||
|
||||
대상 코드가 정해지면 위치와 방식이 기계적으로 결정되어야 한다.
|
||||
|
||||
| 대상 | 레벨 | 협력자 | 위치 |
|
||||
| --- | --- | --- | --- |
|
||||
| 도메인 엔티티·값 객체 | unit | 없음 | 해당 leaf `src/test` |
|
||||
| 유스케이스 | unit | 손으로 만든 Fake (Mockito 아님) | `application-core/src/test` |
|
||||
| 시작 검증기 (`*Validator`) | unit | `MockEnvironment` | `app-bootstrap/src/test` |
|
||||
| `@Configuration` 조립 | slice | `ApplicationContextRunner` | `app-bootstrap/src/test` |
|
||||
| 컨트롤러 | slice | `@WebMvcTest` + 모의 유스케이스 | `adapter/inbound/web/src/test` |
|
||||
| JPA 리포지토리 매핑 | integration | Testcontainers PostgreSQL | `postgresqlIntegrationTest` |
|
||||
| 아웃박스·멱등성 행 수명주기 | integration | Testcontainers PostgreSQL | `app-bootstrap/src/integrationTest` |
|
||||
| 브로커 발행/수신 | integration | 실제 브로커 | leaf 전용 레인 |
|
||||
| 에러 응답 스키마 | contract | 없음 (스냅샷) | `app-bootstrap/src/test/.../contract` |
|
||||
| 의존 방향·패키지 경계 | architecture | 없음 (ArchUnit) | `app-bootstrap/src/test/.../architecture` |
|
||||
|
||||
### 4.1 금지
|
||||
|
||||
- `src/test` 안에서 `org.testcontainers` 의존 — `TestTaxonomyArchitectureTest` 가 막는다 (BOOT-014).
|
||||
- 필요 없는 `@SpringBootTest`. 조립을 검증할 것이 아니면 `ApplicationContextRunner` 나 순수 단위
|
||||
테스트로 충분하다.
|
||||
- slice 애노테이션 혼합 (`@WebMvcTest` + `@DataJpaTest`) — Spring 이 지원하지 않는다.
|
||||
- production 코드가 test fixture 에 의존하는 것.
|
||||
- 픽스처를 `TestUtil`·`CommonUtil` 같은 이름으로 묶는 것. 역할을 드러내는 이름
|
||||
(`fixture/`, `fake/`, `container/`, `assertion/`) 을 쓴다.
|
||||
|
||||
## 5. 공용 테스트 지원 코드 — `testFixtures`
|
||||
|
||||
**표준은 `java-test-fixtures` 하나다 (ADR-BUILD-001).** 공용 테스트 지원 코드는
|
||||
`src/testFixtures/java` 에 두고, 다른 leaf 는 `testFixtures(project(':x'))` 로 소비한다.
|
||||
|
||||
두 관례가 공존하던 상태(BOOT-015)는 해소됐다. `ca.testkit-publisher` 컨벤션 플러그인과 그것을 쓰던
|
||||
`testkit` 소스셋 5개는 모두 이관됐고, 플러그인 자체도 제거됐다. 이관하면서 드러난 사실 하나는 기록해
|
||||
둘 값어치가 있다: `testkit*` 구성이 `testImplementation` 을 상속했기 때문에 fixture 들은 각 leaf 가
|
||||
선언한 모든 테스트 라이브러리를 **말없이** 보고 있었다. `testFixturesImplementation` 으로 옮기면서
|
||||
그 표면이 드러났고, 다섯 leaf 에서 도합 30개가 넘는 의존을 명시적으로 적어야 했다.
|
||||
|
||||
`test` 가 아닌 lane 은 fixture 를 소비한다고 선언해야 한다 — `java-test-fixtures` 는 `test` 만
|
||||
자동으로 배선한다:
|
||||
|
||||
```groovy
|
||||
strictTestLanes {
|
||||
sourceSet('postgresqlIntegrationTest') { compilesAgainst 'main', 'testFixtures' }
|
||||
}
|
||||
```
|
||||
|
||||
디렉터리는 역할을 드러내는 형태를 권고한다 (`fixture/`, `fake/`, `container/`, `assertion/`).
|
||||
`TestUtil`·`CommonUtil` 같은 무의미한 이름 묶음은 금지한다.
|
||||
|
||||
## 6. CI 단계 매핑
|
||||
|
||||
폴더만 나누고 CI 에서 한꺼번에 돌리면 분리의 의미가 없다.
|
||||
|
||||
```
|
||||
커밋 / IDE → unit · slice · contract · architecture (`test`)
|
||||
Pull Request → + integration (integration 레인)
|
||||
머지 / 스테이징 → + build-qualification (functionalTest, sampleOffTest,
|
||||
conditionalTransportTest)
|
||||
야간 / 스케줄 → + qualification · performance
|
||||
```
|
||||
|
||||
`check` 에는 인프라 레인을 붙이지 않는다. 이것은 이 저장소가 이미 따르고 있는 관례이며
|
||||
(`persistence-jpa` 의 `postgresqlIntegrationTest` 도 `check` 에 붙어 있지 않다), Docker 없는
|
||||
환경에서 `check` 가 실패하지 않게 하는 유일한 방법이다.
|
||||
|
||||
## 7. LLM 에이전트에게 적용할 때
|
||||
|
||||
이 저장소는 에이전트 협업을 전제로 설계되어 있다. 테스트 생성을 맡길 때는 다음 순서를 강제한다.
|
||||
|
||||
1. 이 테스트가 §2 의 어느 레벨인지 판정하고 근거를 적는다.
|
||||
2. §3 표에서 해당 소스셋을 찾는다.
|
||||
3. 이미 존재하는 fixture 를 먼저 검색한다.
|
||||
4. 테스트를 작성한다.
|
||||
5. 판정한 레벨보다 큰 레벨로 작성하지 않았는지 확인한다 (`@SpringBootTest` 를 썼다면 왜 필요한지
|
||||
설명할 수 있어야 한다).
|
||||
@@ -1,27 +0,0 @@
|
||||
# Flaky-test quarantine registry — feature-ci-quality-gates-contract §4 (D7 / D9).
|
||||
#
|
||||
# This branch is the flaky-quarantine SSOT. A test that flakes may be tagged with JUnit's built-in
|
||||
# @Tag("quarantine") so it stops blocking the release gate (src/build.gradle: the main `test` task
|
||||
# runs excludeTags 'quarantine'; the bucket runs separately via `./gradlew quarantineTest`,
|
||||
# non-blocking). Quarantine is a TEMPORARY escape, never a parking lot — every quarantined test MUST
|
||||
# be listed here and MUST leave quarantine within 14 days.
|
||||
#
|
||||
# The `verifyQuarantineSunset` Gradle gate (wired into `check`) enforces, on every build:
|
||||
# - schema — each entry has test / quarantined_since / reason / tracking_issue;
|
||||
# - sunset — quarantined_since is within 14 days (older → build fails);
|
||||
# - drift — every @Tag("quarantine") test in src/**/test is registered here (and vice-versa,
|
||||
# a registered test should carry the tag).
|
||||
#
|
||||
# This file lives at the repo ROOT (not docs/, which is gitignored) so it is committed and readable
|
||||
# by CI — same rationale as .trivyignore.yaml. CODEOWNERS governs merge-time approval of changes.
|
||||
#
|
||||
# Schema (one list entry per quarantined test):
|
||||
#
|
||||
# quarantined:
|
||||
# - test: "dev.caskeleton.bootstrap.contract.SomeFlakyContractTest" # FQN, optionally "...#method"
|
||||
# quarantined_since: "2026-06-20" # ISO date; 14-day sunset
|
||||
# reason: "intermittent timeout under shared CI load — suspected fixed-port bind race"
|
||||
# tracking_issue: "https://github.com/<org>/<repo>/issues/123"
|
||||
#
|
||||
# The skeleton ships with an EMPTY bucket: no flaky tests are quarantined.
|
||||
quarantined: []
|
||||
@@ -4,6 +4,15 @@
|
||||
# noise that must not enter the image build context, while keeping everything
|
||||
# the builder stage needs to resolve dependencies and run bootJar.
|
||||
|
||||
# ---- Build recipe itself ----------------------------------------------------
|
||||
# The Dockerfile is supplied with `-f` and is never needed inside the context. Leaving it in means
|
||||
# `COPY . .` embeds it in the image AND makes every Dockerfile edit — a comment included — invalidate
|
||||
# the cached dependency-resolution layer, which costs a full Gradle re-resolve (~3 min) for a change
|
||||
# that affects nothing the builder reads.
|
||||
Dockerfile
|
||||
Dockerfile.*
|
||||
.dockerignore
|
||||
|
||||
# ---- Version control --------------------------------------------------------
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
@@ -11,7 +11,7 @@ SPRING_PROFILES_ACTIVE=local
|
||||
# JPA: needs the PostgreSQL service. Flyway owns the schema from dev onward, and local uses the
|
||||
# same vendor semantics so the two do not diverge.
|
||||
APP_PERSISTENCE_JPA_ENABLED=true
|
||||
APP_DATASOURCE_URL=jdbc:postgresql://localhost:5432/ca_skeleton
|
||||
APP_DATASOURCE_URL=jdbc:postgresql://localhost:5433/ca_skeleton
|
||||
APP_DATASOURCE_USERNAME=ca_skeleton
|
||||
APP_DATASOURCE_PASSWORD=
|
||||
APP_DATASOURCE_DDL_AUTO=validate
|
||||
|
||||
+52
-7
@@ -59,6 +59,27 @@ COPY . .
|
||||
RUN ./gradlew :app-bootstrap:stageDockerJar --no-daemon -x test \
|
||||
-PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"
|
||||
|
||||
# ---- Layer extraction (D8) --------------------------------------------------
|
||||
# Split the uber JAR into Spring Boot's four layers before it reaches the runtime stage.
|
||||
#
|
||||
# Without this the whole fat JAR — every dependency and the application classes in one file — is a
|
||||
# single image layer, so a release that changes one line of application code re-pushes and re-pulls
|
||||
# every dependency in the graph. The layers are ordered least- to most-frequently-changed
|
||||
# (dependencies, loader, snapshot dependencies, application), which is what makes the expensive
|
||||
# layer cacheable across releases.
|
||||
#
|
||||
# `extract` WITHOUT `--launcher` is the layout Spring Boot 4 documents: a thin application JAR whose
|
||||
# manifest Class-Path points at the extracted lib/ directory, rather than a nested-JAR uber JAR the
|
||||
# loader has to open and index on every start. It is also the AOT-cache/CDS-friendly layout, which
|
||||
# is the layout any later startup-time work would need.
|
||||
#
|
||||
# Absolute paths on both sides, and no WORKDIR change: DeveloperExperienceContractTest asserts that
|
||||
# every Dockerfile names the exact Gradle-staged artifact path rather than selecting a JAR, and the
|
||||
# input here is that same fixed path Gradle wrote. Nothing in this stage may pick a JAR by pattern.
|
||||
RUN java -Djarmode=tools \
|
||||
-jar /build/src/app-bootstrap/build/docker/application.jar \
|
||||
extract --layers --destination /build/src/app-bootstrap/build/docker/extracted
|
||||
|
||||
# ---- Stage 2: runtime image -------------------------------------------------
|
||||
# JRE-only slim image (D3: no full JDK in production image).
|
||||
# Uses eclipse-temurin:21-jre-jammy — the Adoptium-supported JRE variant.
|
||||
@@ -130,24 +151,48 @@ VOLUME ["/var/lib/backend/files"]
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder --chown=app:app /build/src/app-bootstrap/build/docker/application.jar app.jar
|
||||
# ---- Application layers (D8) ------------------------------------------------
|
||||
# One COPY per Spring Boot layer, ordered least- to most-frequently-changed. Each COPY is its own
|
||||
# image layer, so a release that only changes application code re-pushes and re-pulls the last one
|
||||
# instead of the whole dependency graph. This replaced a single `COPY application.jar app.jar`,
|
||||
# under which every release shipped every dependency again because they lived in the same file as
|
||||
# the code that changed.
|
||||
#
|
||||
# All four land in /app: the extracted application.jar is a thin JAR whose manifest Class-Path
|
||||
# points at ./lib, which is what the dependencies layer unpacks to.
|
||||
COPY --from=builder --chown=app:app /build/src/app-bootstrap/build/docker/extracted/dependencies/ ./
|
||||
COPY --from=builder --chown=app:app /build/src/app-bootstrap/build/docker/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder --chown=app:app /build/src/app-bootstrap/build/docker/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder --chown=app:app /build/src/app-bootstrap/build/docker/extracted/application/ ./
|
||||
|
||||
USER app
|
||||
|
||||
# ---- Ports ------------------------------------------------------------------
|
||||
# 8080 — application HTTP port
|
||||
# 9001 — management / actuator port (parallel actuator branch wires this endpoint)
|
||||
# 9001 — management / actuator port, from management.server.port in config/observability.yml
|
||||
EXPOSE 8080 9001
|
||||
|
||||
# ---- Health check -----------------------------------------------------------
|
||||
# Targets the actuator readiness probe on the management port (9001).
|
||||
# CROSS-FEATURE COUPLING: the /actuator/health/readiness endpoint is implemented
|
||||
# by the parallel runtime-health + actuator branches. The HEALTHCHECK is wired here
|
||||
# (container-side) and will pass once those branches are merged. In this worktree
|
||||
# the endpoint may return 404; the container will be UNHEALTHY until merged.
|
||||
#
|
||||
# The endpoint is real: config/observability.yml sets management.server.port to 9001 and
|
||||
# management.endpoint.health.probes.enabled to true, which is what publishes
|
||||
# /actuator/health/readiness. (This block used to carry a note saying the path might 404 because
|
||||
# the actuator work lived on an unmerged branch. It has been merged for some time, and a stale
|
||||
# warning about a healthcheck is the kind of comment that gets a real red container ignored.)
|
||||
#
|
||||
# A readiness failure here is a correct UNHEALTHY, not a broken probe: the readiness group includes
|
||||
# the datasource, so a container started with no reachable PostgreSQL is genuinely not ready.
|
||||
# Kubernetes ignores HEALTHCHECK and uses its own probes against the same path; this exists for
|
||||
# docker and Compose.
|
||||
#
|
||||
# wget is present in eclipse-temurin:21-jre-jammy, so nothing is installed for it.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider \
|
||||
http://localhost:9001/actuator/health/readiness || exit 1
|
||||
|
||||
# ---- Entrypoint -------------------------------------------------------------
|
||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||
# The extracted thin JAR, not the uber JAR the builder produced. It carries only application classes
|
||||
# and a Class-Path pointing at ./lib, so the loader does not open and index a nested-JAR archive on
|
||||
# every start, and the layout stays AOT-cache/CDS friendly for any later startup work.
|
||||
ENTRYPOINT ["java", "-jar", "application.jar"]
|
||||
|
||||
+17
-3
@@ -63,6 +63,14 @@ COPY . .
|
||||
RUN ./gradlew :sample-portfolio:stageDockerJar --no-daemon -x test \
|
||||
-PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"
|
||||
|
||||
# ---- Layer extraction -------------------------------------------------------
|
||||
# Identical to src/Dockerfile — see the long note there. Kept in sync because the header of this
|
||||
# file says the builder stages are, and a demo image whose layout has drifted from the release image
|
||||
# stops being a demo of the release image. Absolute paths for the same contract-test reason.
|
||||
RUN java -Djarmode=tools \
|
||||
-jar /build/src/sample-portfolio/build/docker/application.jar \
|
||||
extract --layers --destination /build/src/sample-portfolio/build/docker/extracted
|
||||
|
||||
# ---- Stage 2: runtime image -------------------------------------------------
|
||||
# JRE-only slim image (no full JDK in the demo image either).
|
||||
FROM eclipse-temurin:21-jre-jammy@sha256:199aebeb3adcde4910695cdebfe782ada38dadb6cc8013159b58d3724451befd AS runtime
|
||||
@@ -108,7 +116,12 @@ RUN groupadd --system --gid 1000 app \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/application.jar app.jar
|
||||
# ---- Application layers -----------------------------------------------------
|
||||
# One COPY per Spring Boot layer, least- to most-frequently-changed, matching src/Dockerfile.
|
||||
COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/extracted/dependencies/ ./
|
||||
COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/extracted/spring-boot-loader/ ./
|
||||
COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/extracted/snapshot-dependencies/ ./
|
||||
COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/extracted/application/ ./
|
||||
|
||||
USER app
|
||||
|
||||
@@ -125,5 +138,6 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
||||
http://localhost:9001/actuator/health/readiness || exit 1
|
||||
|
||||
# ---- Entrypoint -------------------------------------------------------------
|
||||
# mainClass (SamplePortfolioApplication) is baked into the bootJar manifest.
|
||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||
# mainClass (SamplePortfolioApplication) is baked into the bootJar manifest and survives the
|
||||
# extraction into the thin application.jar.
|
||||
ENTRYPOINT ["java", "-jar", "application.jar"]
|
||||
|
||||
+48
-57
@@ -18,10 +18,18 @@
|
||||
| --- | --- |
|
||||
| `verifyCleanArchitectureDependencies` | 모듈 간 의존 방향이 허용된 범위 안에 있는지 검사 |
|
||||
| `verifyRuntimeModuleMembership` | registry의 두 composition root membership과 실제 main project dependency가 정확히 일치하는지 검사 |
|
||||
| `verifyEnvKeys` | `env-keys.yaml` ↔ `application.yml` ↔ `src/.env` 가 어긋나지 않는지 검사 |
|
||||
| `verifyOneTypePerFile` | 파일당 public 최상위 타입 1개, 파일명 == 타입명인지 검사 |
|
||||
| `verifyTrivyignore` | `.trivyignore.yaml` 의 Trivy suppression 이 사유·만료일을 갖추고 만료/기한초과가 아닌지 검사 |
|
||||
| `verifyReadmeCommands` | root README의 실행 가능한 Gradle/Compose/Make 명령이 실제 task/file/target과 일치하는지 검사 |
|
||||
| `:app-bootstrap:verifyEnvKeys` | `env-keys.yaml` ↔ `application.yml` ↔ `src/.env.example` ↔ 타입 설정 메타데이터가 어긋나지 않는지 검사 |
|
||||
| `verifyApplicationCoreDependencyPurity` | application-core의 production 의존이 project-only이고 클래스패스에 프레임워크가 없는지 검사 |
|
||||
| `verifyNoIgnoredSourcePackages` | Git이 실을 수 없는 Java 소스 파일이 없는지 검사 |
|
||||
|
||||
`architectureCheck` 하나가 위 네 개를 모두 실행합니다.
|
||||
|
||||
**2026-09에 삭제한 게이트.** `verifyOneTypePerFile`(Checkstyle의 `OneTopLevelClass`가 같은 규칙을
|
||||
파싱된 파일에 대해 검사한다), `verifyTrivyignore`·`verifyQuarantineSunset`(빈 레지스트리를 지키는
|
||||
수백 줄짜리 커스텀 YAML 파서), `verifyReadmeCommands`·`verifyDocumentedLeafCount`·
|
||||
`verifyRunbookReferences`·`verifyTestSourceSetRegistry`(문서 파서),
|
||||
`verifyConfigurationPropertiesProcessor`(`ca.spring-config` convention plugin이 대체).
|
||||
근거는 `docs/superpowers/specs/2026-09-16-verification-surface-reduction-design.md`.
|
||||
|
||||
### Local bootstrap
|
||||
|
||||
@@ -31,12 +39,6 @@ DB와 app lifecycle은 저장소 루트의 base/local Compose 조합이 소유
|
||||
끝나 public health endpoint가 준비되어야 다음 단계로 넘어갑니다. `src/.env`는 env 설정의
|
||||
SSOT이고 bootstrap이 별도 env template을 만들지 않습니다.
|
||||
|
||||
README command drift는 다음 명령으로 독립 실행할 수 있습니다.
|
||||
|
||||
```bash
|
||||
./gradlew verifyReadmeCommands
|
||||
```
|
||||
|
||||
### Traceable version + dependency locking
|
||||
|
||||
- 모든 project version은 `<MAJOR>.<MINOR>.<PATCH>+<12자리 git sha>`입니다. base version은
|
||||
@@ -103,20 +105,24 @@ vendor/build나 container base image까지 byte-for-byte 같음을 주장하지
|
||||
./gradlew conditionalTransportQualification
|
||||
```
|
||||
|
||||
### `verifyOneTypePerFile` (code-conventions I6)
|
||||
### 파일당 public 최상위 타입 1개 (code-conventions I6)
|
||||
|
||||
- **하는 일.** `src/main/java` 의 모든 `.java` 파일이 public 최상위 타입을 1개만 갖고, 그 타입 이름이
|
||||
파일 이름과 같은지 검사합니다 (Google Java Style Guide §3.4.1). `package-info.java`,
|
||||
`module-info.java` 는 예외입니다.
|
||||
- **근거.** 이 "파일 모양(file-shape)" 규칙은 ArchUnit 으로는 잡을 수 없습니다. ArchUnit 은 컴파일된
|
||||
bytecode 를 읽기 때문에 "한 파일에 몇 개의 타입이 있었는지", "파일 이름이 무엇이었는지" 같은 소스
|
||||
파일 레벨 정보를 볼 수 없습니다. 그래서 다른 `verify*` 게이트와 똑같이 기계적으로 강제하려고 소스
|
||||
파일을 직접 스캔하는 별도 태스크로 만들어 `check` 에 연결했습니다.
|
||||
Checkstyle이 소유합니다 — `OneTopLevelClass`와 `OuterTypeFilename`(`config/checkstyle/checkstyle.xml`).
|
||||
각 leaf의 `checkstyleMain`/`checkstyleTest`가 그 leaf의 `check`에서 돕니다.
|
||||
|
||||
### `verifyEnvKeys`
|
||||
`verifyOneTypePerFile`이라는 루트 태스크가 있었고 삭제했습니다. `src/main/java`를 줄 단위 정규식으로
|
||||
읽었고 세 가지가 틀렸습니다: package-private 최상위 타입이 보이지 않았고(126개 main 소스가 한 번도
|
||||
매칭되지 않아, 파일 하나에 package-private 타입 다섯 개가 있어도 통과했다), `src/main/java`만 읽었고,
|
||||
`^public` 앵커 때문에 블록 주석이나 텍스트 블록의 `public`으로 시작하는 줄을 선언으로 셌습니다.
|
||||
Checkstyle은 파싱된 파일에 같은 질문을 하고, leaf 단위로 돕니다.
|
||||
|
||||
- **하는 일.** `docs/registries/env-keys.yaml`, `application.yml`, `src/.env` 세 곳을 lock-step(서로
|
||||
어긋나지 않게) 으로 유지합니다. `env-keys.yaml` 이 `APP_` 키의 SSOT 이고, drift 가 생기면 빌드를
|
||||
### `:app-bootstrap:verifyEnvKeys`
|
||||
|
||||
- **소유.** app-bootstrap. 이 질문("이 애플리케이션의 배포에 무엇을 줘야 하는가")은 composition
|
||||
root의 것이고, `./gradlew :domain-core:check`가 알아야 할 사항이 아닙니다. 루트 집계 이름은
|
||||
`configContractCheck`이고 정의는 `src/gradle/config-contract.gradle`입니다.
|
||||
- **하는 일.** `docs/registries/env-keys.yaml`, `application.yml`, `src/.env.example` 세 곳을
|
||||
lock-step(서로 어긋나지 않게) 으로 유지합니다. `env-keys.yaml` 이 `APP_` 키의 SSOT 이고, drift 가 생기면 빌드를
|
||||
실패시킵니다.
|
||||
- **막으려는 것 3가지.** (1) 필수 env 가 조용히 누락되는 것, (2) 더 이상 쓰지 않는 stale env 키가
|
||||
`.env` 에 남는 것, (3) 실제로 쓰는 `APP_` 키가 registry 에 등록되지 않고 빠져나가는 것.
|
||||
@@ -152,49 +158,34 @@ vendor/build나 container base image까지 byte-for-byte 같음을 주장하지
|
||||
checkout 에서 누락되거나 untracked 상태면 즉시 실패합니다. 승인된 변경만 update task로 재생성한
|
||||
뒤 보안 리뷰와 함께 커밋합니다.
|
||||
|
||||
### `verifyTrivyignore`
|
||||
### Trivy suppression과 플래키 격리 — 정책은 유지, 파서는 삭제
|
||||
|
||||
- **하는 일.** repo 루트 `.trivyignore.yaml` 의 모든 Trivy suppression 항목이 (1) `id`, (2) 비어있지
|
||||
않은 `statement`(사유), (3) 미래이면서 90일 이내인 `expired_at`(만료일) 을 갖추었는지 검사하고,
|
||||
하나라도 빠지거나 이미 만료됐거나 90일을 초과하면 `./gradlew check` 를 실패시킵니다.
|
||||
- **막으려는 것.** 2026-05-25 ca-tmpl audit 에서 발견된 "만료일·사유 없는 suppression 을 추가해
|
||||
취약점을 영구히 조용히 우회"하는 구멍입니다. Trivy 는 `expired_at` 이 없으면 **영구 유효**로
|
||||
취급하므로(공식 문서), 만료일 누락 자체를 차단해야 합니다.
|
||||
- **두 겹의 보완 통제.** 이 게이트는 *필드 검증*(CI), `.github/CODEOWNERS` 는 *merge 승인*(GitHub
|
||||
네이티브)을 담당합니다. CODEOWNERS 는 "누가 파일을 바꿀 수 있는가"만, 이 게이트는 "필드가 갖춰졌는가"
|
||||
만 잡으므로 둘은 대체재가 아니라 보완재입니다.
|
||||
- **결정 — 90일 상한 (프로젝트 선택).** Trivy 문서는 `expired_at` 필드의 *존재*만 보장하고
|
||||
기간 상한은 권고하지 않습니다. 짧으면 재검토 부담이 늘고, 길면 사실상 영구 ignore 가 되는
|
||||
trade-off 에서 90일을 기본값으로 두었습니다. fork 는 `src/build.gradle` 의 `maxWindowDays` 로
|
||||
조정합니다.
|
||||
- **위치.** suppression 파일은 `docs/` 가 아니라 repo 루트(`.trivyignore.yaml`)에 둡니다 — Trivy 가
|
||||
스캔 루트에서 자동으로 읽는 커밋 대상 파일이기 때문입니다. 정책 전문(severity·KEV·license·SLA)은
|
||||
`.github/dependency-vulnerability-policy.md`, CI 배선은 `.github/workflows/dependency-vulnerability.yml`
|
||||
에 있습니다.
|
||||
**Trivy suppression.** repo 루트 `.trivyignore.yaml`이 유일한 suppression 소스이고, 모든 Trivy 호출이
|
||||
`--ignorefile .trivyignore.yaml`로 명시합니다. 항목은 `id`, 비어 있지 않은 `statement`, 90일 이내의
|
||||
미래 `expired_at`을 갖춰야 합니다. 이 규칙은 그대로이고, 강제하는 주체가 `.github/CODEOWNERS` 리뷰어로
|
||||
바뀌었습니다. `verifyTrivyignore`는 105줄짜리 손으로 쓴 YAML 파서였고 — 들여쓰기 추적, 인라인 스칼라
|
||||
처리, 따옴표 제거 — 지키던 파일은 만들어진 이래 계속 비어 있었습니다. 실제 항목이 생기고 그것이
|
||||
drift하기 시작하면 그때 자동화합니다. 진짜 항목을 상대로, 진짜 YAML 라이브러리로.
|
||||
|
||||
### `verifyQuarantineSunset` + 플래키 격리
|
||||
**플래키 격리.** 간헐 실패 테스트에 JUnit 기본 `@Tag("quarantine")`를 붙이면 메인 `test`가
|
||||
`excludeTags 'quarantine'`로 제외하므로 merge를 막지 않고, `./gradlew quarantineTest`(비차단)로만
|
||||
돕니다. 이 두 줄은 유지됩니다.
|
||||
|
||||
- **하는 일.** 플래키(간헐 실패) 테스트는 JUnit 기본 `@Tag("quarantine")` 를 붙여 격리합니다. 메인
|
||||
`test` 태스크는 `excludeTags 'quarantine'` 로 이들을 **릴리스 게이트에서 제외**하므로 플래키 테스트가
|
||||
merge 를 막지 않습니다. 격리된 테스트는 별도 `./gradlew quarantineTest`(비차단, `ignoreFailures`)로만
|
||||
돕니다.
|
||||
- **막으려는 것.** 격리가 *영구 주차장* 이 되는 것. `verifyQuarantineSunset`(루트 태스크, `check` 에
|
||||
연결)이 매 빌드마다 (1) 레지스트리 스키마(`test`/`quarantined_since`/`reason`/`tracking_issue`),
|
||||
(2) **14일 sunset**(`quarantined_since` 가 14일을 넘으면 빌드 실패), (3) **drift**(소스에
|
||||
`@Tag("quarantine")` 가 달렸는데 레지스트리에 없으면 실패)를 검사합니다.
|
||||
- **결정 — 14일 sunset (프로젝트 선택).** Spotify/Google/MS 사례는 격리 버킷의 정당성만
|
||||
보이고(Fowler 는 반대), 14일이라는 정량값·자동 강제는 ca-tmpl 절충안입니다(`company-case-study`
|
||||
강도 — 공식 best practice 아님). fork 는 `src/build.gradle` 의 `sunsetDays` 로 조정합니다.
|
||||
- **위치.** 레지스트리는 `docs/`(gitignore) 가 아니라 repo 루트 `flaky-quarantine.yaml` 에 둡니다 —
|
||||
CI 가 읽어야 하는 커밋 대상 파일이기 때문입니다(`.trivyignore.yaml` 과 같은 이유). 스켈레톤은 빈
|
||||
버킷(`quarantined: []`)으로 출고됩니다.
|
||||
`flaky-quarantine.yaml` 레지스트리와 `verifyQuarantineSunset`(14일 sunset + drift 검사)은
|
||||
삭제했습니다. 250줄짜리 YAML 파서 + Java 렉서(주석과 문자열 리터럴 안의 `@Tag("quarantine")`를
|
||||
걸러내려고 인덱스 보존 렉서를 직접 구현)로 항목이 0개인 레지스트리를 지키고 있었습니다. 순서가
|
||||
반대입니다 — 실제로 격리된 테스트가 생기고, 그게 주차장이 되기 시작할 때 도입할 정책입니다.
|
||||
|
||||
### CI 게이트 배선
|
||||
|
||||
- **소유 범위.** 이 계약은 *게이트 배선*(어떤 게이트가 CI 에서 돌고 실패 시 어떻게 릴리스를 막는가)을
|
||||
소유합니다. 개별 scanner/tool/severity *정책* 은 owner 브랜치가 소유하며, 그 20행 매핑의 in-repo
|
||||
SSOT 가 `.github/ci-gate-matrix.yml` 입니다. `.github/scripts/verify-gate-matrix.sh`(`gate-matrix-lint`
|
||||
잡)가 표 ↔ 실제 task/test/job 정합을 매 PR 마다 cross-check 합니다.
|
||||
SSOT는 Gradle task graph와 GitHub Actions job graph 그 자체입니다.
|
||||
|
||||
`.github/ci-gate-matrix.yml`(1,025줄, 107개 게이트 행)과 `.github/scripts/verify-gate-matrix.sh`
|
||||
(568줄)는 삭제했습니다. 그 표는 이미 두 그래프에 있는 정보의 세 번째 사본이었고, 검사기는 세 사본을
|
||||
서로 같게 유지하는 일을 했습니다. 결과적으로 체크 하나를 추가하려면 Gradle · workflow · 표 ·
|
||||
검사기 기대값 · Java 계약 테스트 다섯 곳을 같이 고쳐야 했습니다.
|
||||
- **워크플로.** `.github/workflows/ci-quality-gates.yml` 의 `release-gate` 잡이 모든 release-blocking
|
||||
게이트의 fan-in(단일 required status check)입니다. 플래키 `quarantine` 잡은 의도적으로 `needs` 에서
|
||||
제외(비차단)됩니다. 위임 게이트(Trivy SCA/이미지 스캔)는 `dependency-vulnerability.yml` 가 소유하며,
|
||||
@@ -342,7 +333,7 @@ ca-skeleton:
|
||||
입력하면 상태와 무관하게 기동을 거부합니다.
|
||||
- 다만 `sample-portfolio`의 application YAML에는 legacy facade를 시연하기 위해 15개 키가 남아
|
||||
있습니다. 이 모듈은 fixture/reference consumer이고 production 의존성이 아니며, 그 YAML은
|
||||
`verifyEnvKeys`가 검사하는 세 파일에 포함되지 않습니다. "제거됐다"는 문장이 저장소 전체를
|
||||
`:app-bootstrap:verifyEnvKeys`가 검사하는 세 파일에 포함되지 않습니다. "제거됐다"는 문장이 저장소 전체를
|
||||
가리킨다고 읽히지 않도록 범위를 명시합니다.
|
||||
- legacy JDK facade가 필요한 fork만 canonical composition 밖에서
|
||||
`OutboundHttpSettings.bindLegacy(Binder)`와 legacy configuration을 명시적으로 import합니다.
|
||||
|
||||
@@ -93,7 +93,7 @@ runtimeClasspath 에 Tomcat 을 올리면서, 동시에 같은 artifact 가 `REA
|
||||
`testCompileClasspath,testRuntimeClasspath` 만). `GraphQlRuntimeTransport` 가 실제 실행 중인
|
||||
서버를 감지해 `backend.graphql.execution-profile` 과 어긋나면 **부팅을 거부**한다.
|
||||
- `annotationProcessor` 로 `spring-boot-configuration-processor` — `GraphQlPlatformProperties` 가
|
||||
`@ConfigurationProperties` 이므로 레포 전역 `verifyConfigurationPropertiesProcessor` 패리티
|
||||
`@ConfigurationProperties` 이므로 `ca.spring-config` convention plugin이 주는 패리티
|
||||
게이트가 이 선언을 요구한다.
|
||||
|
||||
## Forbidden
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
// spring-graphql / graphql-java versions are managed by the Spring Boot BOM, so no explicit
|
||||
// versions or module-scoped platform imports are needed (unlike the grpc adapter, whose io.grpc
|
||||
// coordinates the BOM does not manage).
|
||||
apply plugin: 'ca.spring-library'
|
||||
apply plugin: 'ca.spring-config'
|
||||
|
||||
description = 'Inbound adapter: GraphQL API (Spring for GraphQL, GraphQL execution platform)'
|
||||
|
||||
// The contract suites, the integration fixtures and the in-memory registries are for the people
|
||||
@@ -50,7 +53,6 @@ dependencies {
|
||||
// declaration — an adopter configuring backend.graphql.* gets IDE completion and validation
|
||||
// from the generated metadata rather than from prose. (The prefix is `backend.graphql`; this
|
||||
// comment used to say `spring.graphql.platform.*`, which never existed.)
|
||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
|
||||
// A raw request body can only be capped before something decodes it, and on a servlet stack the
|
||||
// only place that exists is a filter. `compileOnly` is what keeps that from contradicting the
|
||||
|
||||
@@ -5,10 +5,20 @@
|
||||
// NO protobuf: there is no `com.google.protobuf` plugin and no `.proto` here — health + reflection
|
||||
// come from grpc-services at runtime, and a future consuming feature owns its `.proto`/services.
|
||||
//
|
||||
// io.grpc:* / protobuf versions are NOT managed by the Spring Boot BOM, and this repo has no version
|
||||
// catalog, so the grpc-bom + protobuf-bom platforms are imported HERE (module scope) using the root
|
||||
// `ext.grpcVersion` / `ext.protobufVersion` SSOT — this keeps the strict-locking blast radius to
|
||||
// this module (the shared root dependencyManagement block stays io.grpc-free).
|
||||
// io.grpc:* / protobuf versions are NOT managed by the Spring Boot BOM, so the grpc-bom +
|
||||
// protobuf-bom platforms are imported HERE (module scope) using the root `ext.grpcVersion` /
|
||||
// `ext.protobufVersion` SSOT — this keeps the strict-locking blast radius to this module (the shared
|
||||
// root dependencyManagement block stays io.grpc-free).
|
||||
//
|
||||
// This sentence used to end "and this repo has no version catalog", which is false:
|
||||
// gradle/libs.versions.toml exists and this leaf's siblings use it. Module scope is a locking
|
||||
// decision, not a consequence of a missing catalog. The catalog simply has no io.grpc or protobuf
|
||||
// entry, which leaves protobuf with two sources — catalog `protobuf` (used by
|
||||
// adapter:inbound:websocket) and root `ext.protobufVersion` (used here) — on different majors. They
|
||||
// do not meet today because neither leaf is in a composition root; see the W2A handoff.
|
||||
|
||||
apply plugin: 'ca.spring-library'
|
||||
apply plugin: 'ca.spring-config'
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
@@ -29,7 +39,6 @@ dependencies {
|
||||
implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
|
||||
implementation "io.grpc:grpc-services:${grpcVersion}" // health + reflection
|
||||
|
||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
|
||||
// The boot test directly builds generated health/reflection protobuf messages. grpc-services
|
||||
// does not expose protobuf-java on its compile API, so keep the narrower test-only declaration.
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
|
||||
apply plugin: 'ca.spring-library'
|
||||
apply plugin: 'ca.spring-config'
|
||||
|
||||
apply plugin: 'java-test-fixtures'
|
||||
|
||||
// The inbound HTTP API execution platform design models itself as 23 Stable Gradle modules under
|
||||
// modules/web. This repository's fail-closed module registry outranks that layout, so those modules
|
||||
// are packages here and WebModuleBoundaryTest enforces the design's module dependency table. The
|
||||
@@ -13,15 +19,14 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.session:spring-session-core'
|
||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||
implementation('org.openapitools:jackson-databind-nullable:0.2.6') {
|
||||
implementation(libs.jackson.databind.nullable) {
|
||||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
|
||||
}
|
||||
// feature-api-contract-baseline D10: OpenAPI producer. springdoc exposes the
|
||||
// running app's machine-readable contract at /v3/api-docs (OAS 3.1, generated —
|
||||
// never a hand-maintained stale schema). The release-blocking drift gate is
|
||||
// owned by feature-contract-verification-test-suite (planned).
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0'
|
||||
implementation libs.springdoc.openapi.starter.webmvc.api
|
||||
// The two Advanced representations, compile-only on purpose. They were `implementation` first,
|
||||
// on the reasoning that a missing backend would surface as a NoClassDefFoundError at the first
|
||||
// request that negotiated one. That reasoning was wrong about what the jars do: Spring Boot's
|
||||
@@ -65,13 +70,12 @@ tasks.named('test') {
|
||||
// tests and by the composition root. A rule pack that only its own fixture tests import is verified
|
||||
// as library code and applied to nothing — the shape the JPA testkit had to be corrected out of.
|
||||
strictTestLanes {
|
||||
sourceSet('testkit') { compilesAgainst 'main' }
|
||||
// The Jetty compatibility lane is its own source set because it needs a different embedded
|
||||
// server on the classpath. Two servers in one source set means Spring Boot picks one and the
|
||||
// "Jetty" lane silently runs on Tomcat — a compatibility matrix that certifies the same
|
||||
// container twice.
|
||||
sourceSet('jettyCompatTest') {
|
||||
compilesAgainst 'main', 'testkit'
|
||||
compilesAgainst 'main', 'testFixtures'
|
||||
inherits 'implementation'
|
||||
}
|
||||
// Reactor Netty is the Stable WebFlux server baseline and it cannot share a source set with
|
||||
@@ -85,11 +89,11 @@ strictTestLanes {
|
||||
// developer's `check` depend on a container runtime, and the usual outcome of that is an
|
||||
// @Disabled that nobody notices has been there for months.
|
||||
sourceSet('nginxProxyTest') {
|
||||
compilesAgainst 'main', 'testkit'
|
||||
compilesAgainst 'main', 'testFixtures'
|
||||
inherits 'implementation'
|
||||
}
|
||||
sourceSet('webfluxContractTest') {
|
||||
compilesAgainst 'main', 'testkit'
|
||||
compilesAgainst 'main', 'testFixtures'
|
||||
// Inherits nothing. The default is to extend `testImplementation`, which extends the leaf's
|
||||
// own `implementation` and therefore carries spring-boot-starter-web — and with Tomcat on
|
||||
// the classpath Boot deduces a servlet application, starts a servlet container, and the
|
||||
@@ -98,16 +102,36 @@ strictTestLanes {
|
||||
}
|
||||
}
|
||||
|
||||
testkitPublisher {
|
||||
consumedBy 'test'
|
||||
publishAs 'webTestkit'
|
||||
}
|
||||
|
||||
// ArchUnit is declared after the testkit source set exists, because `testkitImplementation` is
|
||||
// ArchUnit is declared after the testkit source set exists, because `testFixturesImplementation` is
|
||||
// created by that declaration.
|
||||
dependencies {
|
||||
testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
||||
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
||||
// The fixtures speak the application's ports, so they compile against it directly. Under the
|
||||
// old source set this arrived through testImplementation inheritance.
|
||||
testFixturesImplementation project(':application-core')
|
||||
testFixturesImplementation project(':shared-contract')
|
||||
|
||||
// Declared rather than inherited: the `testkit` source set extended testImplementation, so the
|
||||
// fixtures compiled against every test library this leaf declared without stating any of them
|
||||
// (ADR-BUILD-001). Listing them makes the fixtures' own surface reviewable.
|
||||
testFixturesImplementation 'jakarta.servlet:jakarta.servlet-api'
|
||||
testFixturesImplementation 'jakarta.validation:jakarta.validation-api'
|
||||
testFixturesImplementation 'org.assertj:assertj-core'
|
||||
testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testFixturesImplementation 'org.springframework:spring-web'
|
||||
testFixturesImplementation 'org.springframework:spring-webmvc'
|
||||
testFixturesImplementation 'org.springframework:spring-context'
|
||||
testFixturesImplementation 'org.springframework:spring-core'
|
||||
testFixturesImplementation 'org.springframework.boot:spring-boot'
|
||||
testFixturesImplementation 'org.springframework.boot:spring-boot-test'
|
||||
testFixturesImplementation 'org.springframework.boot:spring-boot-web-server'
|
||||
testFixturesImplementation 'org.springframework.boot:spring-boot-autoconfigure'
|
||||
testFixturesImplementation 'org.springframework.security:spring-security-config'
|
||||
testFixturesImplementation 'tools.jackson.core:jackson-databind'
|
||||
testFixturesImplementation 'org.springframework.security:spring-security-core'
|
||||
testFixturesImplementation 'org.springframework.security:spring-security-web'
|
||||
testFixturesImplementation 'io.projectreactor:reactor-core'
|
||||
testFixturesImplementation libs.archunit.junit5
|
||||
testImplementation libs.archunit.junit5
|
||||
|
||||
// Jetty replaces Tomcat for this lane only. The exclusion is what makes the lane mean
|
||||
// something: with both on the classpath Boot starts Tomcat and the lane certifies nothing.
|
||||
@@ -153,77 +177,53 @@ dependencies {
|
||||
nginxProxyTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
// The lane task. A release compatibility gate that is not wired to a task is a document.
|
||||
tasks.register('webFluxContractTest', Test) {
|
||||
group = 'verification'
|
||||
strictTestLanes {
|
||||
// A release compatibility gate that is not wired to a task is a document.
|
||||
lane('webFluxContractTest') {
|
||||
sourceSet = 'webfluxContractTest'
|
||||
description = 'Runs the Stable HTTP contract against a real Reactor Netty.'
|
||||
testClassesDirs = sourceSets.webfluxContractTest.output.classesDirs
|
||||
classpath = sourceSets.webfluxContractTest.runtimeClasspath
|
||||
useJUnitPlatform()
|
||||
failOnNoDiscoveredTests = true
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
}
|
||||
customize = { test -> test.jvmArgs '-Duser.timezone=UTC' }
|
||||
}
|
||||
|
||||
// Docker-gated, and it says so rather than skipping. A lane that quietly passes when the container
|
||||
// runtime is missing is a lane that has been certifying nothing since whenever Docker last broke.
|
||||
tasks.register('webNginxProxyTest', Test) {
|
||||
group = 'verification'
|
||||
// Docker-gated, and it says so rather than skipping. A lane that quietly passes when the
|
||||
// container runtime is missing is a lane that has been certifying nothing since whenever Docker
|
||||
// last broke.
|
||||
lane('webNginxProxyTest') {
|
||||
sourceSet = 'nginxProxyTest'
|
||||
description = 'Runs the proxy, prefix and spoofing contract behind a real Nginx.'
|
||||
testClassesDirs = sourceSets.nginxProxyTest.output.classesDirs
|
||||
classpath = sourceSets.nginxProxyTest.runtimeClasspath
|
||||
useJUnitPlatform()
|
||||
failOnNoDiscoveredTests = true
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
}
|
||||
|
||||
// The cross-stack gate. It depends on every recording lane rather than tolerating a missing one:
|
||||
// a parity check that compares whatever happens to be present would report agreement across a
|
||||
// matrix with a hole in it.
|
||||
tasks.register('webCrossStackParityTest', Test) {
|
||||
group = 'verification'
|
||||
description = 'Compares the wire contract recorded by Tomcat, Jetty and Reactor Netty.'
|
||||
testClassesDirs = sourceSets.test.output.classesDirs
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
useJUnitPlatform {
|
||||
includeTags 'web-parity'
|
||||
customize = { test -> test.jvmArgs '-Duser.timezone=UTC' }
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
dependsOn 'test', 'webJettyCompatTest', 'webFluxContractTest'
|
||||
}
|
||||
|
||||
// The Advanced lane. Every capability is off unless a deployment names it, so none of them is
|
||||
// exercised by anything a production deployment runs — which makes a lane that runs them all the
|
||||
// only place a break is noticed before whoever enables it notices.
|
||||
//
|
||||
// They also run inside `test`, deliberately. They are ordinary unit tests, and excluding them from
|
||||
// the PR gate to make this lane look meaningful would mean the PR gate stopped covering a fifth of
|
||||
// the leaf.
|
||||
tasks.register('webAdvancedTest', Test) {
|
||||
group = 'verification'
|
||||
description = 'Runs every web Advanced capability contract.'
|
||||
testClassesDirs = sourceSets.test.output.classesDirs
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
useJUnitPlatform {
|
||||
includeTags 'web-advanced'
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
}
|
||||
|
||||
tasks.register('webJettyCompatTest', Test) {
|
||||
group = 'verification'
|
||||
lane('webJettyCompatTest') {
|
||||
sourceSet = 'jettyCompatTest'
|
||||
description = 'Runs the Stable HTTP contract against a real Jetty instead of Tomcat.'
|
||||
testClassesDirs = sourceSets.jettyCompatTest.output.classesDirs
|
||||
classpath = sourceSets.jettyCompatTest.runtimeClasspath
|
||||
useJUnitPlatform()
|
||||
failOnNoDiscoveredTests = true
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
customize = { test -> test.jvmArgs '-Duser.timezone=UTC' }
|
||||
}
|
||||
|
||||
// The cross-stack gate. It depends on every recording lane rather than tolerating a missing one:
|
||||
// a parity check that compares whatever happens to be present would report agreement across a
|
||||
// matrix with a hole in it.
|
||||
lane('webCrossStackParityTest') {
|
||||
tag = 'web-parity'
|
||||
description = 'Compares the wire contract recorded by Tomcat, Jetty and Reactor Netty.'
|
||||
customize = { test ->
|
||||
test.jvmArgs '-Duser.timezone=UTC'
|
||||
test.dependsOn 'test', 'webJettyCompatTest', 'webFluxContractTest'
|
||||
}
|
||||
}
|
||||
|
||||
// The Advanced lane. Every capability is off unless a deployment names it, so none of them is
|
||||
// exercised by anything a production deployment runs — which makes a lane that runs them all the
|
||||
// only place a break is noticed before whoever enables it notices.
|
||||
//
|
||||
// They also run inside `test`, deliberately. They are ordinary unit tests, and excluding them
|
||||
// from the PR gate to make this lane look meaningful would mean the PR gate stopped covering a
|
||||
// fifth of the leaf.
|
||||
lane('webAdvancedTest') {
|
||||
tag = 'web-advanced'
|
||||
description = 'Runs every web Advanced capability contract.'
|
||||
customize = { test -> test.jvmArgs '-Duser.timezone=UTC' }
|
||||
}
|
||||
}
|
||||
|
||||
strictTestLanes {
|
||||
|
||||
@@ -2,64 +2,64 @@
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
ch.qos.logback:logback-classic:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
ch.qos.logback:logback-core:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
ch.qos.logback:logback-classic:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
ch.qos.logback:logback-core:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.fasterxml.woodstox:woodstox-core:7.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.fasterxml:classmate:1.7.3=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.fasterxml:classmate:1.7.3=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.github.docker-java:docker-java-api:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport-zerodep:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
|
||||
com.github.spotbugs:spotbugs:4.10.2=spotbugs
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
|
||||
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.auto:auto-common:1.2.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.auto:auto-common:1.2.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs
|
||||
com.google.code.gson:gson:2.13.2=spotbugs
|
||||
com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
com.google.errorprone:error_prone_annotations:2.41.0=spotbugs
|
||||
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
|
||||
com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.guava:guava:33.5.0-jre=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.guava:guava:33.5.0-jre=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.guava:guava:33.6.0-jre=checkstyle
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
|
||||
com.jayway.jsonpath:json-path:2.10.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
|
||||
com.tngtech.archunit:archunit-junit5-api:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-engine:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-api:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-engine:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.tngtech.archunit:archunit:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
commons-beanutils:commons-beanutils:1.11.0=checkstyle
|
||||
commons-codec:commons-codec:1.19.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
commons-collections:commons-collections:3.2.2=checkstyle
|
||||
commons-io:commons-io:2.20.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
commons-io:commons-io:2.21.0=spotbugs
|
||||
commons-logging:commons-logging:1.3.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
commons-logging:commons-logging:1.3.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
info.picocli:picocli:4.7.7=checkstyle
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
io.micrometer:micrometer-commons:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.micrometer:micrometer-observation:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
io.micrometer:micrometer-commons:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.micrometer:micrometer-observation:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.netty:netty-buffer:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.netty:netty-codec-base:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.netty:netty-codec-classes-quic:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
@@ -83,27 +83,27 @@ io.netty:netty-transport-native-unix-common:4.2.17.Final=webfluxContractTestComp
|
||||
io.netty:netty-transport:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.projectreactor.netty:reactor-netty-core:1.3.7=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.projectreactor.netty:reactor-netty-http:1.3.7=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.projectreactor:reactor-core:3.8.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.projectreactor:reactor-core:3.8.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.projectreactor:reactor-test:3.8.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.enterprise:jakarta.enterprise.lang-model:4.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.interceptor:jakarta.interceptor-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.servlet:jakarta.servlet-api:6.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.servlet:jakarta.servlet-api:6.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
|
||||
jakarta.transaction:jakarta.transaction-api:2.0.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
jakarta.websocket:jakarta.websocket-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.websocket:jakarta.websocket-client-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
javax.inject:javax.inject:1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
javax.inject:javax.inject:1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
jaxen:jaxen:2.0.6=spotbugs
|
||||
net.bytebuddy:byte-buddy-agent:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
net.java.dev.jna:jna:5.18.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
net.minidev:accessors-smart:2.6.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
net.minidev:json-smart:2.6.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
@@ -111,24 +111,24 @@ net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
|
||||
org.antlr:antlr4-runtime:4.13.2=checkstyle
|
||||
org.apache.bcel:bcel:6.12.0=spotbugs
|
||||
org.apache.commons:commons-compress:1.28.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.20.0=checkstyle,compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.20.0=checkstyle,compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.apache.commons:commons-text:1.15.0=spotbugs
|
||||
org.apache.commons:commons-text:1.3=checkstyle
|
||||
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
|
||||
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
|
||||
org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apache.logging.log4j:log4j-core:2.25.5=spotbugs
|
||||
org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
|
||||
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
|
||||
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
|
||||
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
|
||||
org.apache.tomcat.embed:tomcat-embed-core:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-core:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.apache.xbean:xbean-reflect:3.7=checkstyle
|
||||
org.apiguardian:apiguardian-api:1.1.2=jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
org.assertj:assertj-core:3.27.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.apiguardian:apiguardian-api:1.1.2=jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
org.assertj:assertj-core:3.27.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.awaitility:awaitility:4.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
|
||||
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
|
||||
@@ -167,25 +167,25 @@ org.eclipse.jetty:jetty-session:12.1.12=jettyCompatTestCompileClasspath,jettyCom
|
||||
org.eclipse.jetty:jetty-util:12.1.12=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-xml:12.1.12=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
org.hamcrest:hamcrest:3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||
org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.jetbrains:annotations:17.0.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jettyCompatTestAnnotationProcessor,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestAnnotationProcessor,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestAnnotationProcessor,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jettyCompatTestAnnotationProcessor,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestAnnotationProcessor,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestAnnotationProcessor,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit:junit-bom:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit:junit-bom:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.junit:junit-bom:6.1.0=spotbugs
|
||||
org.mockito:mockito-core:5.20.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,mockitoAgent,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.mockito:mockito-junit-jupiter:5.20.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.objenesis:objenesis:3.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
org.osgi:org.osgi.resource:1.0.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
|
||||
@@ -196,82 +196,82 @@ org.ow2.asm:asm-tree:9.10.1=jettyCompatTestCompileClasspath,jettyCompatTestRunti
|
||||
org.ow2.asm:asm-util:9.10.1=spotbugs
|
||||
org.ow2.asm:asm:9.10.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,spotbugs
|
||||
org.ow2.asm:asm:9.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.pcollections:pcollections:4.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.pcollections:pcollections:4.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
|
||||
org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.reflections:reflections:0.10.2=checkstyle
|
||||
org.rnorth.duct-tape:duct-tape:1.0.8=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
org.skyscreamer:jsonassert:1.5.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.slf4j:slf4j-api:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.slf4j:slf4j-api:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
|
||||
org.slf4j:slf4j-simple:2.0.18=checkstyle
|
||||
org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor
|
||||
org.springframework.boot:spring-boot-http-codec:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-http-converter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-http-converter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jetty:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-netty:4.0.8=webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-reactor-netty:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-reactor:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-resttestclient:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-servlet:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-servlet:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jetty-runtime:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jetty:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-reactor-netty:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-web:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-web:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webflux:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webmvc:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-web-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-web-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webflux:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.security:spring-security-config:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-jose:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-resource-server:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.boot:spring-boot:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework.security:spring-security-config:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-jose:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-resource-server:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-test:7.0.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-web:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.session:spring-session-core:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework:spring-aop:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-beans:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-context:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-core:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-expression:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-test:7.0.9=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-web:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-webflux:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-webmvc:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.security:spring-security-web:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework.session:spring-session-core:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.springframework:spring-aop:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-beans:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-context:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-core:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-expression:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-test:7.0.9=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-web:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-webflux:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.springframework:spring-webmvc:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
org.testcontainers:testcontainers-junit-jupiter:2.0.5=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
org.testcontainers:testcontainers:2.0.5=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
|
||||
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
|
||||
org.xmlunit:xmlunit-core:2.10.4=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.yaml:snakeyaml:2.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
org.yaml:snakeyaml:2.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
tools.jackson.dataformat:jackson-dataformat-xml:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
|
||||
empty=
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public final class HmacWebCursorCodec implements WebCursorCodec {
|
||||
String canonical;
|
||||
try {
|
||||
canonical = new String(DECODER.decode(encodedBody), StandardCharsets.UTF_8);
|
||||
} catch (IllegalArgumentException notBase64) {
|
||||
} catch (IllegalArgumentException malformedEncoding) {
|
||||
throw new WebCursorException();
|
||||
}
|
||||
String[] fields = canonical.split("\\u001f", -1);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user