refactor: 각 어댑터터별 리펙토링 진행
This commit is contained in:
@@ -45,6 +45,96 @@ gates:
|
||||
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
|
||||
@@ -287,3 +377,15 @@ gates:
|
||||
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
|
||||
|
||||
@@ -27,8 +27,15 @@ 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.
|
||||
readonly EXPECTED_GATE_COUNT=38
|
||||
# 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
|
||||
@@ -135,6 +142,25 @@ gradle_custom_task_is_registered_in_build_file() {
|
||||
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:]]*/ {
|
||||
@@ -159,14 +185,64 @@ gradle_custom_task_is_registered_in_build_file() {
|
||||
' "${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
|
||||
while IFS= read -r -d '' build_file; do
|
||||
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 < <(find "${REPO_ROOT}/src" -type f -name '*.gradle' -print0)
|
||||
done <<< "${GRADLE_FILE_CACHE}"
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -325,9 +401,16 @@ while IFS=$'\t' read -r id blocking mechanism ref workflow job execution; do
|
||||
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" ]] \
|
||||
&& ! grep -RqsE -- "dependsOn.*named\\(['\"]${ref}['\"]\\)" "${REPO_ROOT}/src" \
|
||||
--include='build.gradle'; then
|
||||
&& ! gradle_custom_task_wired_into_check "${ref}"; then
|
||||
failures+=("gate '${id}' task '${ref}' exists but is not wired into Gradle check")
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -27,11 +27,12 @@ readonly EXPECTED_WORKFLOW_LOCK=(
|
||||
'3be84c9f15fa3b2ac5a085f8d725ec6d05e7007ae0b433da9e79b3bf340d57ea .github/workflows/jpa-next-hibernate8.yml'
|
||||
'a2b74bfb3af12d6d03cd2ea8a5e48490dd131afb89b79694d498c5798387ac53 .github/workflows/jpa-next-jpa4.yml'
|
||||
'cd955ef4af895df477896dad9577810f010b2beea8570b09b008f9e94e928bd0 .github/workflows/jpa-next-postgresql19.yml'
|
||||
'b56b548a867b74eaeccb42e7df4f4e52cf7ce657ab27f91e2c8d7ea9944d64af .github/workflows/jpa-nightly.yml'
|
||||
'21e065880ef5d4c4ff973f52d8107ef08398ebaf9518ec6b2fd82d49c5d822c6 .github/workflows/jpa-nightly.yml'
|
||||
'04851f44ba94533bfbc8fabe2b3a2b408726a9996e86ed3864986d1499d16b50 .github/workflows/jpa-pr.yml'
|
||||
'59cb3a0ffc687a15eefe96bc5e3a70d42be78e1cc85d2e7f7880dac6124ca4c7 .github/workflows/jpa-r2-evidence.yml'
|
||||
'4748f2ba0a0b77dc1a858ebcfa7db6e41627d97843df5f0aa978bc2facccaad2 .github/workflows/jpa-release.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'
|
||||
|
||||
@@ -119,10 +119,14 @@ jobs:
|
||||
src/**/*.gradle
|
||||
src/**/gradle-wrapper.properties
|
||||
src/**/gradle.lockfile
|
||||
- name: Measure pool saturation and REQUIRES_NEW pressure
|
||||
- name: Verify pool saturation and REQUIRES_NEW connection behaviour
|
||||
working-directory: src
|
||||
# Machine-dependent bounds are reported rather than asserted unless explicitly enabled, so a
|
||||
# noisy shared runner does not produce a red build that means nothing.
|
||||
# A behaviour contract, not a measurement. This step used to switch assertions off with an
|
||||
# explicit property and call the result a certification, so the only threshold it ever
|
||||
# asserted was that thresholds were not being asserted. What
|
||||
# it checks now — that REQUIRES_NEW needs two connections per concurrent thread, that a
|
||||
# saturated pool reports its pending count, that a caller waits rather than proceeding
|
||||
# without a connection — is true on any runner, so there is nothing to switch off.
|
||||
run: >-
|
||||
./gradlew
|
||||
:adapter:outbound:persistence-jpa:jpaPlatformPoolContractTest
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
name: jpa-release
|
||||
|
||||
# The release gate. Every item in docs/jpa/support-matrix.md's gate table has a job or an assertion
|
||||
# here, and JpaReleaseManifest parses that document so a gate removed from the docs fails the build
|
||||
# The release gate. src/config/jpa/release-registry.json is the source: every gate it declares has a
|
||||
# job or an assertion here, JpaReleaseRenderingTest holds this file's matrix and promotion lists to
|
||||
# the registry's Stable majors, and verifyJpaReleaseGateTasks resolves each gate's task against the
|
||||
# real Gradle graph. So a gate removed from the registry, or a major demoted in it, fails the build
|
||||
# rather than quietly ceasing to be checked.
|
||||
#
|
||||
# The matrix below is therefore not free to drift: editing it without editing the registry fails the
|
||||
# unit lane.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# The messaging platform's broker certification lane.
|
||||
#
|
||||
# Separate from ci-quality-gates.yml because it needs a container runtime and several minutes of it.
|
||||
# The lane deliberately carries no Docker guard: every other container suite in the messaging tree
|
||||
# skips with a stated reason when Docker is absent, and a certification lane that skipped would
|
||||
# report success for a broker nobody started — which is the exact claim the evidence exists to rule
|
||||
# out.
|
||||
#
|
||||
# The job runs the evidence gate rather than the lane, and the gate depends on the lane. What it
|
||||
# proves is not only that the scenarios pass but that the committed manifest
|
||||
# (messaging-testkit/src/main/resources/messaging/broker-certification-evidence.jsonl) is what this
|
||||
# run produced, so "certified against a live broker" cannot be restored by editing a file.
|
||||
name: messaging-certification
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "src/messaging/**"
|
||||
- ".github/workflows/messaging-certification.yml"
|
||||
schedule:
|
||||
- cron: "41 4 * * 3"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# Reuse would hand one scenario the broker another scenario had already faulted.
|
||||
TESTCONTAINERS_REUSE_ENABLE: "false"
|
||||
|
||||
jobs:
|
||||
broker-certification:
|
||||
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
|
||||
- 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"
|
||||
# is a claim about one source tree.
|
||||
run: ./gradlew :messaging:messaging-kafka:verifyMessagingCertificationEvidence --no-daemon --stacktrace
|
||||
- name: Publish the certification evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: messaging-broker-certification-evidence
|
||||
path: src/messaging/messaging-kafka/build/messaging-certification/
|
||||
if-no-files-found: warn
|
||||
Reference in New Issue
Block a user