감사 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>
272 lines
14 KiB
YAML
272 lines
14 KiB
YAML
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 documentation-drift gates. They used to be `dependsOn` of the root `check`, so a README
|
|
# sentence about a renamed task failed a compile-and-test run and the fix was to edit a document
|
|
# before unrelated code could build. src/build.gradle now aggregates them as
|
|
# `verifyDocumentationContracts` and leaves them out of `check`. That demotion is only half a
|
|
# change: a gate nothing invokes has not been demoted, it has been deleted. This job is the other
|
|
# half, and it is the reason the four gates still run at all.
|
|
#
|
|
# 2. The lanes that need a machine that is not simultaneously compiling something else — load,
|
|
# abuse, graceful shutdown, TCP fault injection, resource bounds. They were web-nightly.yml and
|
|
# httpclient-nightly.yml, two module-shaped files whose only real difference was the cadence they
|
|
# shared. They now run on every push to main as well as nightly, which is strictly more often
|
|
# than before.
|
|
#
|
|
# 3. Lanes that were registered in Gradle and invoked by nothing. Ten Gradle tasks — six MongoDB
|
|
# 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
|
|
|
|
jobs:
|
|
# verifyReadmeCommands, verifyDocumentedLeafCount, verifyRunbookReferences and
|
|
# verifyTestSourceSetRegistry, as one task. Named as the aggregate rather than as four steps so
|
|
# that adding a fifth documentation gate is a build-file edit and not a workflow edit — and so
|
|
# that the demotion out of `check` has exactly one consumer to point at.
|
|
documentation-contracts:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
|
- name: Validate Gradle wrapper
|
|
id: gradle-wrapper-validation
|
|
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
|
- uses: ./.github/actions/setup-gradle-java
|
|
- name: Verify the documentation contracts
|
|
working-directory: src
|
|
run: ./gradlew verifyDocumentationContracts --no-daemon --stacktrace
|
|
|
|
# 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
|
|
- name: Validate Gradle wrapper
|
|
id: gradle-wrapper-validation
|
|
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
|
- uses: ./.github/actions/setup-gradle-java
|
|
- name: Run the load, abuse and shutdown lanes on every container
|
|
working-directory: src
|
|
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
|
|
- name: Validate Gradle wrapper
|
|
id: gradle-wrapper-validation
|
|
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
|
- uses: ./.github/actions/setup-gradle-java
|
|
- name: Inject TCP faults against a real upstream
|
|
working-directory: src
|
|
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
|
|
- name: Validate Gradle wrapper
|
|
id: gradle-wrapper-validation
|
|
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
|
- uses: ./.github/actions/setup-gradle-java
|
|
- name: Certify pool, streaming, retry, and rotation bounds
|
|
working-directory: src
|
|
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
|
|
# in .github/ci-gate-matrix.yml as release_blocking: false so that "this job cannot fail the
|
|
# build" is written down rather than inferred from a field two hundred lines into a workflow.
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
|
- name: Validate Gradle wrapper
|
|
id: gradle-wrapper-validation
|
|
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
|
- uses: ./.github/actions/setup-gradle-java
|
|
- name: Exercise the experimental HTTP/3 opt-in
|
|
working-directory: src
|
|
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
|
|
# .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
|