fix(build,ci): 정의만 되고 안 돌던 레인 13개를 배선하고 버전 11개를 카탈로그로
감사 remediation 의 마지막 항목 두 개. ## 아무 데서도 안 돌던 레인 등록 태스크 97개 중 어느 CI 경로로도 도달하지 않는 것이 20개였다. 있는 줄 알았는데 안 도는 상태 — 이번에 고친 `*CleanArchitectureTest` 와 같은 종류의 문제다. 각각 판단해서 처리했고, 삭제는 0건이다. - stage 2(`integration-main.yml`, push:main + 03:00) 에 잡 3개 신설: mongo 컨테이너 레인 6개 / messaging 계약 증거 3개 / app-bootstrap integrationTest. 컨테이너가 필요한 레인은 PR 에 두지 않는다 — PR 예산은 5분이고, 단계를 나눈 이유가 이것이다 - stage 3(`release.yml`) 에 `grpc-stable-release-gate` 신설 (inprocess/netty/fault) + `app-image-release` 의 needs 로 연결 - 수동 확정 3개: `grpcPerformanceTest`, `openapiCheckSnapshot`(드리프트 검사는 이미 stage 1 의 `check` 안에 있고 이 태스크는 승인 지점), `sampleOffCompile`(stage 1 `sampleOffTest` 의 진부분집합). 전용 레지스트리 대신 루트 README 에 적었다 — `verifyReadmeCommands` 가 거기 적힌 태스크의 실재를 검증하므로, 문서가 곧 검사 대상이 된다 - 게이트 매트릭스 행 11개 신설. 잡↔행 양방향 대조 결과 68개 잡 전부 행이 있고 행 없는 잡도, 어디서도 안 도는 잡도 없다 측정이 틀린 4건은 배선하지 않았다 — 이미 도달하고 있었다: `jpaPlatformReleaseGate`(`jpaReleaseGate dependsOn`), `generateJpaEvidenceManifests`(`verifyJpaCandidateEvidence` 경유), `messagingCertificationTest`(`verifyMessagingCertificationEvidence` 경유), `stageDockerJar`(호출자가 Gradle 이 아니라 `release.yml` 의 `docker build`). ## 버전 카탈로그 이관 카탈로그를 우회해 문자열로 박혀 있던 값 11개를 `gradle/libs.versions.toml` 로 옮겼다. plugin 5개는 `[plugins]` + `alias(...)`, 툴 3개는 `libs.versions.*.get()`. `grpcVersion`/`protobufVersion`/`awsSdkVersion` 은 이관이 불가하다고 넘어온 항목이었으나, `ext.x` 를 접근자로 남기고 값만 카탈로그에서 읽으면 소비 파일 9개와 `ca.grpc-platform-module.gradle:28` 의 `findProperty` 계약이 그대로이고 해석 결과도 동일하다. **lockfile 재생성 0건.** `commons-lang3` / `netty` 는 BOM 오버라이드라 그대로 둔다 — 오버라이드하는 이유가 주석과 분리되면 값만 남고 근거가 사라진다. ## 검증 (깨끗한 체크아웃, 커밋 전) `verify-gate-matrix.sh` → 107 gates, 101 verified, drift 0 · `verify-gradle-wrapper.sh` PASS · 워크플로 YAML 21개 파싱 OK · `gradlew help` · `verifyCleanArchitectureDependencies` · `build-logic test` · `:app-bootstrap:test` **1001 tests 실패 0** · `:domain-core:check` · `verifyDocumentationContracts` · `verifyDependencyLocks` · `verifyReadmeCommands`. ## 남은 문제 mongo 6레인 · `bootstrap-integration` · messaging 매니페스트 스키마 검증은 CI 에서 한 번도 돈 적이 없다. Docker 가 없으면 실패하도록 설계돼 있으므로 **첫 main push 와 03:00 run 이 빨간 것이 정상 시나리오**다. 로컬에서 Docker 레인을 돌려보지 않았고, `mongo-container-lanes` 의 timeout 90분은 실측이 아니라 추정치다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9bc2e75fe5
commit
40ee9f1e83
+108
-2
@@ -487,6 +487,82 @@ gates:
|
||||
job: documentation-contracts
|
||||
execution: job
|
||||
#
|
||||
# The six Docker-backed MongoDB lanes. Registered in the leaf, excluded from `test` by tag, absent
|
||||
# from `check`, and named by no workflow until now — scripts/verify-mongodb-platform.sh was the
|
||||
# only caller and nothing in .github runs it. One row per lane because each is its own single-line
|
||||
# command, which is what makes `execution: explicit` checkable.
|
||||
#
|
||||
# release_blocking: false is the honest value, not a demotion: stage 2 runs after the merge, so no
|
||||
# release gate can wait on it. Promoting these to stage 1 would put a replica set, a three-node
|
||||
# failover and a Toxiproxy on the pull-request budget.
|
||||
- id: mongo-replica-set-lane
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: mongoReplicaSetTest
|
||||
workflow: integration-main.yml
|
||||
job: mongo-container-lanes
|
||||
execution: explicit
|
||||
- id: mongo-failover-lane
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: mongoFailoverTest
|
||||
workflow: integration-main.yml
|
||||
job: mongo-container-lanes
|
||||
execution: explicit
|
||||
- id: mongo-migration-lane
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: mongoMigrationTest
|
||||
workflow: integration-main.yml
|
||||
job: mongo-container-lanes
|
||||
execution: explicit
|
||||
- id: mongo-compatibility-lane
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: mongoCompatibilityTest
|
||||
workflow: integration-main.yml
|
||||
job: mongo-container-lanes
|
||||
execution: explicit
|
||||
- id: mongo-security-integration-lane
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: mongoSecurityIntegrationTest
|
||||
workflow: integration-main.yml
|
||||
job: mongo-container-lanes
|
||||
execution: explicit
|
||||
- id: mongo-performance-lane
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: mongoPerformanceTest
|
||||
workflow: integration-main.yml
|
||||
job: mongo-container-lanes
|
||||
execution: explicit
|
||||
#
|
||||
# The messaging contract evidence DAG. One row, because one job is one control and the control is
|
||||
# the aggregate: `verifyMessagingContracts` reaches five strict qualification tasks, and through
|
||||
# them `prepareMessagingContractEvidence`, plus both manifest schema validators
|
||||
# (validateMessagingJsonSchemaV1EvidenceManifestSchema by dependsOn,
|
||||
# validateMessagingContractsEvidenceManifestSchema by finalizedBy). Strict qualification tasks are
|
||||
# registered outside `check` by design, so before this job none of the seven executed anywhere.
|
||||
- id: messaging-contract-evidence
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: verifyMessagingContracts
|
||||
workflow: integration-main.yml
|
||||
job: messaging-contract-evidence
|
||||
execution: explicit
|
||||
#
|
||||
# app-bootstrap's Testcontainers lane. Its own source set exists so that `:app-bootstrap:test`
|
||||
# needs no Docker daemon; the unclosed consequence was that a source set outside `test` is also
|
||||
# outside `check`, so these contracts compiled everywhere and ran nowhere.
|
||||
- id: bootstrap-integration-contracts
|
||||
release_blocking: false
|
||||
mechanism: gradle-custom-task
|
||||
ref: integrationTest
|
||||
workflow: integration-main.yml
|
||||
job: bootstrap-integration
|
||||
execution: explicit
|
||||
#
|
||||
# notification-platform.yml — `pr` is path-filtered and also runs on push to main; nightly-chaos
|
||||
# is schedule/dispatch only. The release-blocking notification controls are the verifyNotification*
|
||||
# rows above, which run inside check on every pull request.
|
||||
@@ -768,7 +844,7 @@ gates:
|
||||
job: httpclient-release-gate
|
||||
execution: explicit
|
||||
#
|
||||
# Tag-triggered (`v*`, `web-v*`). The Stable web release gate.
|
||||
# Tag-triggered (`v*`). The Stable web release gate.
|
||||
- id: web-stable-release-gate
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
@@ -777,7 +853,7 @@ gates:
|
||||
job: web-stable-release-gate
|
||||
execution: job
|
||||
#
|
||||
# Tag-triggered (`v*`, `websocket-v*`). The Stable websocket release gate.
|
||||
# Tag-triggered (`v*`). The Stable websocket release gate.
|
||||
- id: websocket-stable-release-gate
|
||||
release_blocking: true
|
||||
mechanism: workflow-job
|
||||
@@ -786,6 +862,36 @@ gates:
|
||||
job: websocket-stable-release-gate
|
||||
execution: job
|
||||
#
|
||||
# Tag-triggered (`v*`). The three gRPC certification lanes, one row each because each runs as its
|
||||
# own single-line command and `execution: explicit` is only verifiable that way.
|
||||
#
|
||||
# These were registered lanes that no workflow named. Their tests are not new coverage — the
|
||||
# `grpc-inprocess`, `grpc-netty` and `grpc-fault` tags run inside `:grpc:grpc-testkit:test`, which
|
||||
# is inside `check` — so what the lanes add is the fail-on-nothing-discovered guard that a tag
|
||||
# filter inside `test` cannot give you. Same argument as web-advanced and websocket-advanced above,
|
||||
# and the same placement.
|
||||
- id: grpc-inprocess-contract-lane
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: grpcInProcessContractTest
|
||||
workflow: release.yml
|
||||
job: grpc-stable-release-gate
|
||||
execution: explicit
|
||||
- id: grpc-netty-transport-lane
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: grpcNettyContractTest
|
||||
workflow: release.yml
|
||||
job: grpc-stable-release-gate
|
||||
execution: explicit
|
||||
- id: grpc-fault-lane
|
||||
release_blocking: true
|
||||
mechanism: gradle-custom-task
|
||||
ref: grpcFaultTest
|
||||
workflow: release.yml
|
||||
job: grpc-stable-release-gate
|
||||
execution: explicit
|
||||
#
|
||||
# Tag-triggered (`v*`). The only job in this repository that produces a deployable artifact.
|
||||
#
|
||||
# One row, not three, because one job is one control: the job builds the app-bootstrap image,
|
||||
|
||||
@@ -34,7 +34,7 @@ readonly EXPECTED_WORKFLOW_LOCK=(
|
||||
'2fa9c8081df1679c1feb9aa101aff47d7d2c24995c155aff6d1e4799eaad8f21 .github/workflows/fileserver-nightly.yml'
|
||||
'1686b7b637611c8cd5eb87b2cc759f5cd2c6b878154363fc336c16b93c635ada .github/workflows/fileserver-pr.yml'
|
||||
'b47932200c9ac9db57070b43bc70c40c89c152e9235d7a1325baab407df215e9 .github/workflows/fileserver-release.yml'
|
||||
'3dc5a3e989043725133a1bbc90636c261406fafaf0158672323ae29dda95c5dd .github/workflows/integration-main.yml'
|
||||
'a18a0f08982b393177a843c1bdd03a881d9d12491819cebb44b6891a87ff2a6d .github/workflows/integration-main.yml'
|
||||
'4345d5cfb5a139a11cf3647c58fff61ab08397ace186919cdc7a769cdfc4d4b7 .github/workflows/jpa-next-hibernate8.yml'
|
||||
'726b3d91603a2529205d1d5568253b57d85fcbb9d10d3efe182491c9da744d78 .github/workflows/jpa-next-jpa4.yml'
|
||||
'3c073a928dfb266051a1a52f4d66bf6d6903b9dbd2cdb6459fab661228f27e88 .github/workflows/jpa-next-postgresql19.yml'
|
||||
@@ -47,7 +47,7 @@ readonly EXPECTED_WORKFLOW_LOCK=(
|
||||
'e685bc846108503ee2cf1e06b6cec040174d49348bd205400f891828f24dda68 .github/workflows/object-storage-qualification.yml'
|
||||
'67ef53adb80551629a482e2610a0753dd0fadf85f523e985c4693354df543748 .github/workflows/pr-adapters.yml'
|
||||
'376a71f7a2b9990e1e96937ad3dd46a33f266cc742ca499b208bc909897b67f3 .github/workflows/redis-sdk-topology.yml'
|
||||
'3f1ff34053bb455587ab9f03305331310b4df4aa4a49bddf875b6969c9afaa05 .github/workflows/release.yml'
|
||||
'42b57385c1f87170ba6d882345c709c11dff019f1860e72ad989b0c5c1a67ece .github/workflows/release.yml'
|
||||
)
|
||||
readonly EXPECTED_COMPOSITE_ACTION_LOCK=(
|
||||
'7ec6591f26a1bd76658c55472e16b195b80db2c4792b429efda5a0dcbde61a45 .github/actions/setup-gradle-java/action.yml'
|
||||
|
||||
@@ -22,6 +22,13 @@ name: integration-main
|
||||
# shared. They now run on every push to main as well as nightly, which is strictly more often
|
||||
# than before.
|
||||
#
|
||||
# 3. Lanes that were registered in Gradle and invoked by nothing. Ten Gradle tasks — six MongoDB
|
||||
# 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
|
||||
@@ -154,3 +161,111 @@ jobs:
|
||||
-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
|
||||
# .github/scripts/verify-gate-matrix.sh reads these command lines to prove each registered lane 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
|
||||
- name: Validate Gradle wrapper
|
||||
id: gradle-wrapper-validation
|
||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Single-node replica set contract lane
|
||||
working-directory: src
|
||||
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
|
||||
- name: Validate Gradle wrapper
|
||||
id: gradle-wrapper-validation
|
||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Qualify the messaging contract, catalog, binding and schema evidence
|
||||
working-directory: src
|
||||
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
|
||||
- name: Validate Gradle wrapper
|
||||
id: gradle-wrapper-validation
|
||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: Run the real-PostgreSQL integration contracts
|
||||
working-directory: src
|
||||
run: ./gradlew :app-bootstrap:integrationTest --no-daemon --stacktrace
|
||||
|
||||
@@ -131,6 +131,47 @@ jobs:
|
||||
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
|
||||
- name: Validate Gradle wrapper
|
||||
id: gradle-wrapper-validation
|
||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
||||
- uses: ./.github/actions/setup-gradle-java
|
||||
- name: In-process contract lane
|
||||
working-directory: src
|
||||
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 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
|
||||
@@ -185,6 +226,7 @@ jobs:
|
||||
- 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
|
||||
|
||||
@@ -122,6 +122,31 @@ cd src
|
||||
|
||||
두 검증 축은 [ci-quality-gates.yml](.github/workflows/ci-quality-gates.yml)의 release gate에 연결되어, 규칙 위반이 병합·릴리스를 막습니다.
|
||||
|
||||
## 수동 전용 Gradle 태스크
|
||||
|
||||
아래 세 태스크는 **어떤 워크플로도 실행하지 않으며, 그게 의도다.** 자동 실행이 틀린 이유를 각각
|
||||
적어 둔다. `verifyReadmeCommands`가 이 블록의 태스크 이름이 실재하는지 검사하므로, 태스크를 지우거나
|
||||
이름을 바꾸면 이 문서가 같이 틀어지고 게이트가 그것을 잡는다.
|
||||
|
||||
```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)
|
||||
|
||||
+29
-22
@@ -9,12 +9,16 @@ import org.gradle.api.tasks.bundling.Jar
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '4.0.8' apply false
|
||||
id 'io.spring.dependency-management' version '1.1.6' apply false
|
||||
// Versions come from gradle/libs.versions.toml, not from this block. They used to be five
|
||||
// literals here and three more inside configure(subprojects) below, which is how the errorprone
|
||||
// plugin and the errorprone checker jar it puts on the compile classpath ended up pinned in two
|
||||
// different files with nothing able to show them together.
|
||||
alias(libs.plugins.spring.boot) apply false
|
||||
alias(libs.plugins.spring.dependency.management) apply false
|
||||
// feature-static-analysis-quality-contract — static analysis / code quality baseline.
|
||||
id 'com.diffplug.spotless' version '8.6.0' apply false // D1 formatter (google-java-format)
|
||||
id 'com.github.spotbugs' version '6.5.6' apply false // D3 bytecode bug finder (+ D4 FindSecBugs)
|
||||
id 'net.ltgt.errorprone' version '5.1.0' apply false // D5 compile-time checker
|
||||
alias(libs.plugins.spotless) apply false // D1 formatter (google-java-format)
|
||||
alias(libs.plugins.spotbugs) apply false // D3 bytecode bug finder (+ D4 FindSecBugs)
|
||||
alias(libs.plugins.errorprone) apply false // D5 compile-time checker
|
||||
// Convention plugins from the included build-logic build. No version: an included build supplies
|
||||
// the plugin itself, so there is no coordinate to drift. `apply false` puts them on the
|
||||
// classpath for the configure(subprojects) block below to apply per leaf.
|
||||
@@ -143,25 +147,28 @@ messagingVerificationSkeletons.each { String taskName, List<String> evidencePath
|
||||
// io.grpc:grpc-bom + protobuf-bom platforms (not the shared dependencyManagement block below),
|
||||
// keeping the strict-locking blast radius to the grpc module alone.
|
||||
//
|
||||
// NOT the single SSOT for protobuf, and the comment here used to claim it was — it said "this repo
|
||||
// has no version catalog", which is false: gradle/libs.versions.toml exists and this same file
|
||||
// reads it (see libs.findsecbugs.plugin / libs.errorprone.core / libs.jmh.core below). Three
|
||||
// protobuf versions are live today and only two of them are visible in the catalog's comparison:
|
||||
// ext.protobufVersion 3.25.5 (here) -> adapter/inbound/grpc
|
||||
// libs protobuf 4.33.2 (catalog) -> adapter/inbound/websocket
|
||||
// libs protobufLegacy 4.29.3 (catalog) -> messaging/messaging-schema-protobuf
|
||||
// Moving these three `ext` pins into gradle/libs.versions.toml is the fix; it also rewrites the
|
||||
// consuming module build.gradle files and regenerates their lockfiles, so it is tracked separately
|
||||
// rather than done here.
|
||||
ext.grpcVersion = '1.68.1'
|
||||
ext.protobufVersion = '3.25.5'
|
||||
// The numbers now live in gradle/libs.versions.toml. These two lines stay because they are the
|
||||
// accessor, not the answer: eight leaf build files interpolate `${grpcVersion}` / `${protobufVersion}`
|
||||
// into coordinate strings, and ca.grpc-platform-module.gradle — a convention plugin in the included
|
||||
// build — reads `rootProject.findProperty('grpcVersion')` and fails closed when it is absent. Moving
|
||||
// the number is what the catalog is for; moving the accessor would rewrite those nine files and
|
||||
// regenerate their lockfiles for no change in what resolves.
|
||||
//
|
||||
// All three live protobuf versions are visible side by side in the catalog now, which is the whole
|
||||
// point — the comment here used to say "this repo has no version catalog", which was already false:
|
||||
// libs protobufGrpc 3.25.5 -> adapter/inbound/grpc (read through ext.protobufVersion)
|
||||
// libs protobuf 4.33.2 -> adapter/inbound/websocket
|
||||
// libs protobufLegacy 4.29.3 -> messaging/messaging-schema-protobuf
|
||||
ext.grpcVersion = libs.versions.grpc.get()
|
||||
ext.protobufVersion = libs.versions.protobufGrpc.get()
|
||||
|
||||
// Outbound objectstorage adapter (adapter:outbound:objectstorage) — the Spring Boot BOM does NOT
|
||||
// manage software.amazon.awssdk:* versions. Pinned here so the objectstorage module imports
|
||||
// software.amazon.awssdk:bom as a platform at MODULE scope (not the shared dependencyManagement
|
||||
// block below) — mirroring the grpc approach above and keeping the strict-locking blast radius to
|
||||
// the objectstorage module alone. Same catalog caveat as above.
|
||||
ext.awsSdkVersion = '2.30.0'
|
||||
// the objectstorage module alone. The version is in the catalog; this is the accessor three leaves
|
||||
// interpolate, for the same reason as `ext.grpcVersion` above.
|
||||
ext.awsSdkVersion = libs.versions.awsSdk.get()
|
||||
|
||||
apply from: "${rootProject.projectDir}/gradle/archive-hygiene.gradle"
|
||||
apply from: "${rootProject.projectDir}/gradle/public-path-snapshot.gradle"
|
||||
@@ -389,7 +396,7 @@ configure(subprojects.findAll { it.childProjects.isEmpty() }) {
|
||||
// spotlessCheck (wired into check) verifies. CI must NEVER run spotlessApply.
|
||||
spotless {
|
||||
java {
|
||||
googleJavaFormat('1.35.0')
|
||||
googleJavaFormat(libs.versions.googleJavaFormat.get())
|
||||
importOrder()
|
||||
removeUnusedImports()
|
||||
}
|
||||
@@ -397,7 +404,7 @@ configure(subprojects.findAll { it.childProjects.isEmpty() }) {
|
||||
|
||||
// D2 — naming + logical ruleset; formatter-owned modules suppressed in the XML.
|
||||
checkstyle {
|
||||
toolVersion = '13.5.0'
|
||||
toolVersion = libs.versions.checkstyle.get()
|
||||
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
|
||||
configDirectory = rootProject.file('config/checkstyle')
|
||||
ignoreFailures = false
|
||||
@@ -414,7 +421,7 @@ configure(subprojects.findAll { it.childProjects.isEmpty() }) {
|
||||
// — strictness is a user trade-off; default is functionally valid). Confirmed false positives
|
||||
// go in config/spotbugs/exclude.xml.
|
||||
spotbugs {
|
||||
toolVersion = '4.10.2'
|
||||
toolVersion = libs.versions.spotbugs.get()
|
||||
reportLevel = com.github.spotbugs.snom.Confidence.valueOf('HIGH')
|
||||
excludeFilter = rootProject.file('config/spotbugs/exclude.xml')
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Version catalog — every dependency version this build pins itself.
|
||||
#
|
||||
# Versions managed by an imported BOM (Spring Boot, AWS SDK v2) are NOT here: a BOM already owns
|
||||
# them, and restating a managed version would create a second answer to the same question. What is
|
||||
# here is the set a module used to spell out inline, once per usage.
|
||||
# Versions managed by an imported BOM are NOT here: a BOM already owns them, and restating a managed
|
||||
# version would create a second answer to the same question. The BOM's own coordinate is a different
|
||||
# question and it is here — `awsSdk` and `grpc` below are the versions of the BOMs themselves, which
|
||||
# this build does choose. What is here is the set a module used to spell out inline, once per usage.
|
||||
#
|
||||
# The reason is visibility, not reproducibility — `gradle.lockfile` already fixes what resolves.
|
||||
# Before this file, 59 coordinate strings were scattered across 15 build files and nothing could
|
||||
@@ -14,15 +15,44 @@
|
||||
# Both are kept as separate aliases rather than unified. Unifying them here would change what
|
||||
# resolves, in a change whose whole purpose is that it does not; the aliases make the split
|
||||
# reviewable, and whoever converges them does so deliberately with the lockfiles regenerated.
|
||||
#
|
||||
# Tool and plugin versions live here too. They are not dependencies of any module, so they never
|
||||
# appear in a gradle.lockfile and nothing but this file can show them side by side. Four of them
|
||||
# (Spring Boot, spotless, spotbugs, errorprone) were spelled out in the root `plugins {}` block, and
|
||||
# three more (google-java-format, the Checkstyle tool, the SpotBugs tool) inside the shared
|
||||
# `configure(subprojects)` block; the errorprone *plugin* and the errorprone *checker* it runs were
|
||||
# pinned in two different files, which is exactly the pair this file exists to put next to each
|
||||
# other.
|
||||
#
|
||||
# What is deliberately NOT here: `ext['commons-lang3.version']` and `ext['netty.version']` in
|
||||
# src/build.gradle. Neither is a version this build chooses — both are overrides of a version the
|
||||
# Spring Boot BOM already manages, and each is valid only next to the sentence that says what breaks
|
||||
# without it (a SpotBugs worker NoClassDefFoundError, and two named CVEs on shared runtime surface).
|
||||
# Moving the number here would leave the reason behind.
|
||||
|
||||
[versions]
|
||||
approvaltests = "31.0.0"
|
||||
archunit = "1.3.0"
|
||||
avro = "1.12.0"
|
||||
# AWS SDK v2 BOM coordinate. Imported at MODULE scope by adapter:outbound:objectstorage,
|
||||
# sample-portfolio and app-bootstrap through `ext.awsSdkVersion`, which reads this key.
|
||||
awsSdk = "2.30.0"
|
||||
blockhound = "1.0.17.RELEASE"
|
||||
# Checkstyle *tool* version (the `checkstyle` extension), not a plugin: Checkstyle is a Gradle
|
||||
# built-in plugin with no coordinate of its own.
|
||||
checkstyle = "13.5.0"
|
||||
cloudevents = "4.0.1"
|
||||
errorprone = "2.49.0"
|
||||
# The Gradle plugin that runs the checker above. Two different artifacts, two different version
|
||||
# lines, deliberately adjacent so a bump of one is visibly not a bump of the other.
|
||||
errorpronePlugin = "5.1.0"
|
||||
findsecbugs = "1.14.0"
|
||||
# Spotless's google-java-format step. A formatter version is a repository-wide decision: a bump
|
||||
# reformats every file.
|
||||
googleJavaFormat = "1.35.0"
|
||||
# io.grpc BOM. Read through `ext.grpcVersion`; adapter:inbound:grpc and the five grpc:* leaves
|
||||
# import it at MODULE scope to keep the strict-locking blast radius off the shared platform.
|
||||
grpc = "1.68.1"
|
||||
jmh = "1.37"
|
||||
jnats = "2.26.2"
|
||||
jqwik = "1.9.1"
|
||||
@@ -34,6 +64,10 @@ junitJupiter = "5.11.3"
|
||||
logstashLogbackEncoder = "8.0"
|
||||
okhttp = "4.12.0"
|
||||
protobuf = "4.33.2"
|
||||
# The third live protobuf version, and the reason this file now shows all three. adapter:inbound:grpc
|
||||
# imports protobuf-bom at this major to match the gRPC BOM above; it is read through
|
||||
# `ext.protobufVersion`.
|
||||
protobufGrpc = "3.25.5"
|
||||
# The websocket leaf's proto contract is generated against an older runtime and has not been
|
||||
# requalified; see the coordinate's own comment in that leaf.
|
||||
protobufLegacy = "4.29.3"
|
||||
@@ -42,7 +76,13 @@ resilience4j = "2.2.0"
|
||||
# Two Groovy generations, deliberately not merged. See the header.
|
||||
spockGroovy4 = "2.3-groovy-4.0"
|
||||
spockGroovy5 = "2.4-groovy-5.0"
|
||||
# SpotBugs *tool* version (the `spotbugs` extension). The plugin that runs it is spotbugsPlugin.
|
||||
spotbugs = "4.10.2"
|
||||
spotbugsPlugin = "6.5.6"
|
||||
spotless = "8.6.0"
|
||||
springBoot = "4.0.8"
|
||||
springCloudContext = "4.1.4"
|
||||
springDependencyManagement = "1.1.6"
|
||||
springDotenv = "4.0.0"
|
||||
springdoc = "3.0.0"
|
||||
toxiproxy = "2.1.7"
|
||||
@@ -84,3 +124,17 @@ spring-dotenv = { module = "me.paulschwarz:spring-dotenv", version.ref = "spring
|
||||
springdoc-openapi-starter-webmvc-api = { module = "org.springdoc:springdoc-openapi-starter-webmvc-api", version.ref = "springdoc" }
|
||||
toxiproxy-java = { module = "eu.rekawek.toxiproxy:toxiproxy-java", version.ref = "toxiproxy" }
|
||||
uuid-creator = { module = "com.github.f4b6a3:uuid-creator", version.ref = "uuidCreator" }
|
||||
|
||||
# Plugin coordinates for the root `plugins {}` block. Declared here so that a plugin version and the
|
||||
# library version it governs cannot drift in two files — errorprone is the live example: the plugin
|
||||
# (errorpronePlugin) and the checker jar it puts on the compile classpath (errorprone, referenced by
|
||||
# libs.errorprone.core below) are now one screen apart.
|
||||
#
|
||||
# The convention plugins from the included build (`ca.*`) are NOT here and cannot be: an included
|
||||
# build supplies the plugin itself, so there is no coordinate and no version to pin.
|
||||
[plugins]
|
||||
spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" }
|
||||
spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "springDependencyManagement" }
|
||||
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
|
||||
spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugsPlugin" }
|
||||
errorprone = { id = "net.ltgt.errorprone", version.ref = "errorpronePlugin" }
|
||||
|
||||
Reference in New Issue
Block a user