빌드·CI 레이어 전수 감사(133건) 중 Track A 에서 처리한 E등급 19건을 뺀
나머지를 마무리한다. 한 커밋인 이유는 서로 얽혀 있기 때문이다 — 워크플로가
`checkstyleMain` 을 부르려면 루트가 먼저 Checkstyle 을 붙여야 하고, 모듈 43개가
convention 을 적용하려면 build-logic 이 먼저 그 플러그인을 등록해야 한다.
영역별로 쪼개면 중간 커밋이 빌드되지 않는다.
## CI 단계 분리 (워크플로 29파일 3,360줄 → 19파일 2,692줄, 잡 69 → 64)
모듈이 아니라 단계로 자른다. 기존 28개는 `web-*` `jpa-*` 처럼 모듈로 갈려
있어서 같은 일이 파일마다 중복됐다.
- stage 1 `ci-quality-gates.yml` + `pr-adapters.yml`(신규, 잡 단위 경로 필터) — pull_request
- stage 2 `integration-main.yml`(신규) — push:main + 03:00. 문서 게이트는 여기에 둔다
- stage 3 `release.yml`(신규, 릴리스 워크플로 7개 중 5개 흡수) — push: tags v*
setup 블록 59회 복붙 → `.github/actions/setup-gradle-java` 1개(잡당 13줄 → 5줄).
잡 8개 삭제, 각각 대체 잡을 확인했다. `verifyCleanArchitectureDependencies` 실행
횟수가 태그당 9 → 6, PR당 8 → 4 로 줄었다.
## 컨테이너 릴리스 신설
이미지를 만드는 것이 아무것도 없었다. Dockerfile 은 있었지만
build-push-action / bootBuildImage / jib 사용처가 0건이고, `*-release.yml` 8개는
테스트 후 아티팩트만 올렸다 — 이름만 릴리스였다.
Boot 레이어드 추출 + thin-JAR 엔트리포인트로 Dockerfile 을 고치고 릴리스
워크플로에 이미지 빌드·푸시·SBOM·스캔을 넣었다. 로컬 빌드로 검증했다:
레지스트리 content 241MB, 기동 3.7초, uid 1000, 헬스체크 200.
코드만 바뀐 릴리스는 7.68MB 만 재푸시한다(이전이라면 156MB).
CI 는 배포하지 않는다 — 매니페스트와 ArgoCD 는 별도 repo 로 간다.
## 게이트 정리
- gate-matrix 의 개수 고정 해제: `EXPECTED_GATE_COUNT=49` 와 하드코딩된 49개 id
목록을 지우고 불변식으로 대체(필드·enum, 워크플로/잡 실재, id 중복,
`release_blocking: true` 는 실제로 release-gate 의 needs 여야 함).
행을 추가하려면 테스트부터 고쳐야 하던 구조를 풀었다. 커버리지 8/28 → 28/28
- 문서 게이트 4개를 `check` 에서 떼어 `verifyDocumentationContracts` 로 묶고
stage 2 에 배치. 어겨도 런타임은 멀쩡하므로 개발을 막지 않는다
- `verifyOneTypePerFile`(정규식 Java 파싱, 126파일 미탐) → Checkstyle
`OneTopLevelClass` + `OuterTypeFilename`. main 위반 0건, test 의 fixture 29건은
정책을 넓히지 않고 suppressions 에 사유와 함께 명시 제외
- leaf 하나의 `check` 가 끌고 오던 저장소 전역 게이트 18개를 재배치.
`:domain-core:check` 가 13 태스크 11초로 끝난다
- convention 플러그인 2개 신설(`ca.platform-module`, `ca.grpc-platform-module`),
플랫폼 모듈 43개에 적용. 손수 짠 Test 태스크 17개를 `strictTestLanes` 로 전환
(태스크 이름 전부 보존 — CI 가 이름으로 부른다)
- `ca.api-surface` 의 정규식 Java 파서를 javac parse-only 로 교체
(기존 베이스라인 3개와 바이트 동일 확인)
- 죽은 태스크 5개 삭제, `src/gradle` 1,713 → 1,440줄, 모듈 build.gradle
3,072 → 2,977줄
## 검사가 검사를 못 하고 있던 것들
- 11개 계약 테스트가 gitignore 된 `src/.env` 를 요구했다. `.gitignore` 자신이
"examples beside it are the tracked contract, never a real one" 이라고 적어둔
규칙과 어긋난다. 깨끗한 체크아웃에는 그 파일이 없으므로 CI 에서 돌 수 없었다.
추적되는 `.env.example` 로 돌린다
- **`.env.local.example` 이 5432 를 가리키는데 compose 는 5433 을 게시한다.**
이 파일을 복사해 시작하는 신규 개발자는 DB 연결에 실패한다. 이걸 잡으라고
만든 테스트가 추적 안 되는 파일을 읽어서, 이미 설정이 끝난 머신에서만 돌고
정작 처음 받는 사람에겐 아무 검사도 안 하고 있었다. 포트를 고치고 테스트를
추적 파일로 돌렸다
- `MongoModuleBoundaryTest` 의 `DO_NOT_INCLUDE_JARS` 때문에 임포트가 0개가 되어
규칙 10개가 "failed to check any classes" 로 실패 중이었다. 이 레인에서는
모듈 자기 클래스가 jar 로 올라온다. `importPackages(ROOT)` 가 이미 서드파티를
거르므로 옵션은 불필요했다
- `ReleaseManifestTaskExistenceTest` 가 build 파일 텍스트에서 `tasks.register(`
만 찾아, convention 의 `lane('...')` 로 바뀐 태스크를 미등록으로 오판했다
- `ProfileSeparationContractTest` 는 런처가 주입하는 `src/.env` 가 맞는 대상이라
그대로 두되, 파일이 없으면 명시적으로 skip 한다 — "안 돌았다" 가 "통과했다"
로 읽히지 않게
## 검증 (전부 깨끗한 체크아웃에서, 커밋 전에 실행)
`verify-gradle-wrapper.sh` PASS · `verify-gate-matrix.sh` OK(drift 0) ·
워크플로 YAML 전수 파싱 OK · actionlint 지적 0 · `gradlew help` ·
`verifyCleanArchitectureDependencies` · `build-logic test` ·
`:app-bootstrap:test` **1001 tests 실패 0 스킵 5** · `:domain-core:check` ·
`verifyDocumentationContracts`.
## 남은 문제
- 첫 `v*` 태그는 이미지 취약점 스캔에서 실패한다(CRITICAL/HIGH 9건:
ubuntu 베이스 2, tomcat-embed-core 3, amqp-client 3, httpcore5 2).
억제를 넣지 않았다 — 릴리스 1회차를 초록으로 만들려고 임계값을 내리면
게이트가 장식이 된다. 의존성·베이스 갱신이 선행돼야 한다
- `fileserver-v*` / `web-v*` / `websocket-v*` 태그는 이제 아무 run 도 만들지
않는다(배포 단위가 하나라는 결정에 따른 것)
- main push 마다 무거운 레인 3개가 새로 돈다 — 러너 분이 늘어난다
- `ProfileSeparationContractTest` 가 찾아낸 4개 값(cache command-timeout,
cache positive-soft-ttl, idempotency provider, rate-limit command-timeout)이
`.env.example` 과 인라인 기본값 사이에서 갈린다. 런타임 설정 판단이라
건드리지 않았다
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
920 lines
36 KiB
YAML
920 lines
36 KiB
YAML
# Every CI control in this checkout: one row per job in .github/workflows, plus the delegated-pending
|
|
# rows for controls that are real and that no workflow runs.
|
|
#
|
|
# The rows are grouped by STAGE, because that is how the workflows are now split. Stage 1 blocks a
|
|
# merge, stage 2 reports on main after the merge, stage 3 stops a release. A control's stage is the
|
|
# honest form of "does this gate development", and it is what release_blocking below records.
|
|
#
|
|
# Image signing and provenance attestation are still absent. So is any join between the image build
|
|
# in release.yml and the two evidence workflows that answer the same release tag from their own files
|
|
# (jpa-release.yml, fileserver-release.yml); that join is registered delegated-pending rather than
|
|
# left unstated. .github/scripts/verify-gate-matrix.sh checks every row against the repository and is
|
|
# itself the gate-matrix-lint job below; it pins no gate count, so registering a new control is
|
|
# adding a row here and nothing else.
|
|
#
|
|
# 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
|
|
#
|
|
# release_blocking is verified, not declared:
|
|
# true a release gate waits on this job — it is ci-quality-gates.yml::release-gate itself,
|
|
# one of that job's `needs:`, a name in its REQUIRED_CHECKS, or a job in a workflow
|
|
# that only runs on a release tag. The lint fails a `true` row that is none of those.
|
|
# conditional the control is real and gates something, but no release gate can require it: it runs
|
|
# behind a path filter, a schedule, a manual dispatch, or an input, so its check run
|
|
# does not exist for every commit a release gate sees.
|
|
# false advisory. A failure here is a signal, not a stop.
|
|
gates:
|
|
# =================================================================================
|
|
# STAGE 1 — pull request. These block a merge.
|
|
# =================================================================================
|
|
#
|
|
# ci-quality-gates.yml — the repository-wide gate. No path filter: it runs on every pull request
|
|
# and every push to main, which is what makes the leaf test suites, the architecture dependency
|
|
# gate and the Checkstyle ruleset below cover a diff without any per-adapter job repeating them.
|
|
- 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: 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: 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
|
|
#
|
|
# Checkstyle, not the retired regex. verifyOneTypePerFile parsed Java with
|
|
# `^public\s+...` line by line; OneTopLevelClass and OuterTypeFilename ask the same two questions
|
|
# against a parsed file and see the package-private top-level types the regex could not. The task
|
|
# still exists in src/build.gradle as an aggregate of every leaf's checkstyleMain, but no workflow
|
|
# names it any more, so this row names what actually runs: checkstyleMain, inside `check`.
|
|
- id: one-type-per-file
|
|
release_blocking: true
|
|
mechanism: gradle-plugin-task
|
|
ref: checkstyle@checkstyleMain
|
|
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
|
|
#
|
|
# Points at the inventory guard rather than at one suite. The 87 architecture rules used to be
|
|
# 74-in-one-class plus a scattered remainder, so naming CleanArchitectureTest.java named most of
|
|
# them and silently omitted the rest; after the split (BOOT-012) it would have named a fifth of
|
|
# them. ArchRuleDiscoveryContractTest pins every rule in the package by name and fails if one is
|
|
# declared where ArchUnit cannot discover it, which is the property this gate actually exists to
|
|
# protect.
|
|
- id: architecture-contract-test
|
|
release_blocking: true
|
|
mechanism: contract-test
|
|
ref: app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.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: 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: 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
|
|
#
|
|
# pr-adapters.yml — the lanes `check` cannot reach: a second servlet container, a real Nginx,
|
|
# Reactor Netty, one PostgreSQL major per job, one HTTP transport per job. Each job is behind a
|
|
# per-job path filter computed from the pull request's diff, so no release gate can wait on one.
|
|
- id: httpclient-contract-stable-contract
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: httpclient-stable-contract
|
|
workflow: pr-adapters.yml
|
|
job: httpclient-stable-contract
|
|
execution: job
|
|
- id: httpclient-security-and-compatibility
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: httpclient-security-and-compatibility
|
|
workflow: pr-adapters.yml
|
|
job: httpclient-security-and-compatibility
|
|
execution: job
|
|
- id: jpa-postgresql-contract
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: jpa-postgresql-contract
|
|
workflow: pr-adapters.yml
|
|
job: jpa-postgresql-contract
|
|
execution: job
|
|
- id: jpa-migration-smoke
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: jpa-migration-smoke
|
|
workflow: pr-adapters.yml
|
|
job: jpa-migration-smoke
|
|
execution: job
|
|
- id: web-cross-stack-parity
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: web-cross-stack-parity
|
|
workflow: pr-adapters.yml
|
|
job: web-cross-stack-parity
|
|
execution: job
|
|
- id: web-nginx-proxy-contract
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: web-nginx-proxy-contract
|
|
workflow: pr-adapters.yml
|
|
job: web-nginx-proxy-contract
|
|
execution: job
|
|
- id: websocket-container-matrix
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: websocket-container-matrix
|
|
workflow: pr-adapters.yml
|
|
job: websocket-container-matrix
|
|
execution: job
|
|
- id: websocket-nginx-contract
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: websocket-nginx-contract
|
|
workflow: pr-adapters.yml
|
|
job: websocket-nginx-contract
|
|
execution: job
|
|
#
|
|
# The job every lane in that file reads. It fails closed on an unreadable diff rather than
|
|
# reporting that nothing changed, because a filter that answers false on a broken comparison
|
|
# turns off every gate behind it and reports green.
|
|
- id: pr-adapter-change-filter
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: changes
|
|
workflow: pr-adapters.yml
|
|
job: changes
|
|
execution: job
|
|
#
|
|
# fileserver-pr.yml — path-filtered pull-request gates. They stay in their own file because
|
|
# FileserverDocumentationCoverageTest requires every job id docs/fileserver/support-matrix.md
|
|
# names to be defined in a .github/workflows/fileserver-*.yml.
|
|
#
|
|
# Path-filtered pull-request gates. They are the only automated check of the fileserver's
|
|
# ext4 and HTTP contracts, but they start only when the filtered paths change, so no release
|
|
# gate can wait on them.
|
|
- id: fileserver-unit-and-architecture
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: fileserver-unit-and-architecture
|
|
workflow: fileserver-pr.yml
|
|
job: fileserver-unit-and-architecture
|
|
execution: job
|
|
- id: fileserver-local-ext4-contract
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: fileserver-local-ext4-contract
|
|
workflow: fileserver-pr.yml
|
|
job: fileserver-local-ext4-contract
|
|
execution: job
|
|
- id: fileserver-http-contract
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: fileserver-http-contract
|
|
workflow: fileserver-pr.yml
|
|
job: fileserver-http-contract
|
|
execution: job
|
|
- id: fileserver-security-suite
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: fileserver-security-suite
|
|
workflow: fileserver-pr.yml
|
|
job: fileserver-security-suite
|
|
execution: job
|
|
- id: fileserver-bounded-memory
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: fileserver-bounded-memory
|
|
workflow: fileserver-pr.yml
|
|
job: fileserver-bounded-memory
|
|
execution: job
|
|
#
|
|
# dependency-vulnerability.yml — supply chain. trivy-fs is the one cross-workflow check
|
|
# ci-quality-gates.yml::release-gate requires by name through REQUIRED_CHECKS.
|
|
- 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
|
|
#
|
|
# link-check.yml — committed documentation links, path-filtered.
|
|
- id: documentation-links
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: lychee
|
|
workflow: link-check.yml
|
|
job: lychee
|
|
execution: job
|
|
|
|
# =================================================================================
|
|
# STAGE 2 — merged state. These do not block a merge; the merge already happened.
|
|
# =================================================================================
|
|
#
|
|
# integration-main.yml — push to main, nightly, or dispatch. The documentation-drift gates that
|
|
# left `check`, and the lanes that need a machine which is not simultaneously compiling.
|
|
- id: documented-leaf-count
|
|
release_blocking: false
|
|
mechanism: gradle-custom-task
|
|
ref: verifyDocumentedLeafCount
|
|
workflow: integration-main.yml
|
|
job: documentation-contracts
|
|
execution: job
|
|
- id: runbook-reference-drift
|
|
release_blocking: false
|
|
mechanism: gradle-custom-task
|
|
ref: verifyRunbookReferences
|
|
workflow: integration-main.yml
|
|
job: documentation-contracts
|
|
execution: job
|
|
- id: readme-command-drift
|
|
release_blocking: false
|
|
mechanism: gradle-custom-task
|
|
ref: verifyReadmeCommands
|
|
workflow: integration-main.yml
|
|
job: documentation-contracts
|
|
execution: job
|
|
#
|
|
# Scheduled. httpclient-nightly-http3-experimental is continue-on-error on purpose: HTTP/3 is
|
|
# an opt-in experiment (-Phttp3.tests.enabled) and a red experiment must not bury a real
|
|
# nightly regression. Registered false so the matrix says that out loud instead of leaving a
|
|
# job that cannot fail unrecorded.
|
|
- id: httpclient-nightly-fault-injection
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: httpclient-fault-injection
|
|
workflow: integration-main.yml
|
|
job: httpclient-fault-injection
|
|
execution: job
|
|
- id: httpclient-nightly-performance
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: httpclient-performance
|
|
workflow: integration-main.yml
|
|
job: httpclient-performance
|
|
execution: job
|
|
- id: httpclient-nightly-http3-experimental
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: httpclient-http3-experimental
|
|
workflow: integration-main.yml
|
|
job: httpclient-http3-experimental
|
|
execution: job
|
|
#
|
|
# Scheduled load, abuse and shutdown lane.
|
|
- id: web-load-abuse-and-shutdown
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: web-load-abuse-and-shutdown
|
|
workflow: integration-main.yml
|
|
job: web-load-abuse-and-shutdown
|
|
execution: job
|
|
#
|
|
# The four documentation-drift gates, as one task. They were `dependsOn` of the root `check`
|
|
# and are not any more: a README sentence about a renamed task should not fail a
|
|
# compile-and-test run. This job is what keeps that a demotion rather than a deletion — with
|
|
# no workflow invoking the aggregate, all four would run nowhere. release_blocking: false is
|
|
# the demotion stated as a fact the lint can check.
|
|
- id: documentation-contracts
|
|
release_blocking: false
|
|
mechanism: gradle-custom-task
|
|
ref: verifyDocumentationContracts
|
|
workflow: integration-main.yml
|
|
job: documentation-contracts
|
|
execution: explicit
|
|
#
|
|
# Ran inside verifyDocumentationContracts above rather than named in the job, which is why
|
|
# this row is execution: job. It had no row at all before.
|
|
- id: test-source-set-registry
|
|
release_blocking: false
|
|
mechanism: gradle-custom-task
|
|
ref: verifyTestSourceSetRegistry
|
|
workflow: integration-main.yml
|
|
job: documentation-contracts
|
|
execution: job
|
|
#
|
|
# 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.
|
|
#
|
|
# `pr` is path-filtered (and 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.
|
|
- id: notification-platform-pr
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: pr
|
|
workflow: notification-platform.yml
|
|
job: pr
|
|
execution: job
|
|
- id: notification-platform-nightly-chaos
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: nightly-chaos
|
|
workflow: notification-platform.yml
|
|
job: nightly-chaos
|
|
execution: job
|
|
#
|
|
# messaging-certification.yml — path-filtered pull request plus a weekly schedule.
|
|
#
|
|
# 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: conditional
|
|
mechanism: gradle-custom-task
|
|
ref: verifyMessagingCertificationEvidence
|
|
workflow: messaging-certification.yml
|
|
job: broker-certification
|
|
execution: explicit
|
|
#
|
|
# object-storage-qualification.yml — pull request, weekly schedule, and two protected dispatch
|
|
# inputs for the AWS sandbox lane.
|
|
#
|
|
# These three ran under `release_blocking: true` while nothing waited on them. object-storage-
|
|
# qualification.yml has no push-to-main trigger and messaging-certification.yml is behind a path
|
|
# filter, so neither produces a check run for every commit ci-quality-gates.yml::release-gate
|
|
# judges; requiring them there would hang on the commits where they never start. They are
|
|
# conditional — real controls on their own trigger — until someone decides to widen that trigger,
|
|
# which is a CI-minutes decision about MinIO and Kafka containers, not a wiring oversight.
|
|
- id: object-storage-minio-managed-contract
|
|
release_blocking: conditional
|
|
mechanism: gradle-custom-task
|
|
ref: objectStorageMinioContractTest
|
|
workflow: object-storage-qualification.yml
|
|
job: minio-managed-contract
|
|
execution: explicit
|
|
- id: poster-image-migration
|
|
release_blocking: conditional
|
|
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
|
|
#
|
|
# redis-sdk-topology.yml — path-filtered pull request (standalone only) and the nightly matrix.
|
|
#
|
|
# 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
|
|
#
|
|
# The lane that topology-evidence needs. Same trigger as the evidence row above it.
|
|
- id: redis-sdk-topology-lanes
|
|
release_blocking: conditional
|
|
mechanism: workflow-job
|
|
ref: lanes
|
|
workflow: redis-sdk-topology.yml
|
|
job: lanes
|
|
execution: job
|
|
#
|
|
# fileserver-nightly.yml — scheduled only. NFS ambiguity, process-kill and lease behaviour need
|
|
# hours and real filesystems; a failure is a signal to act on, not a stop on a release that did not
|
|
# cause it.
|
|
#
|
|
# Scheduled only. NFS ambiguity, process-kill and lease behaviour need hours and real
|
|
# filesystems; a failure is a signal to act on, not a stop on a release that did not cause it.
|
|
- id: fileserver-nfs-ambiguity
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: fileserver-nfs-ambiguity
|
|
workflow: fileserver-nightly.yml
|
|
job: fileserver-nfs-ambiguity
|
|
execution: job
|
|
- id: fileserver-process-kill-matrix
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: fileserver-process-kill-matrix
|
|
workflow: fileserver-nightly.yml
|
|
job: fileserver-process-kill-matrix
|
|
execution: job
|
|
- id: fileserver-large-file-performance
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: fileserver-large-file-performance
|
|
workflow: fileserver-nightly.yml
|
|
job: fileserver-large-file-performance
|
|
execution: job
|
|
- id: fileserver-multi-instance-lease
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: fileserver-multi-instance-lease
|
|
workflow: fileserver-nightly.yml
|
|
job: fileserver-multi-instance-lease
|
|
execution: job
|
|
#
|
|
# jpa-nightly.yml — the middle of the PostgreSQL matrix and the suites too slow or too
|
|
# Docker-heavy for a pull request.
|
|
#
|
|
# Scheduled JPA matrix, failure injection, query-plan/security and pool pressure.
|
|
- id: jpa-full-matrix
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: jpa-full-matrix
|
|
workflow: jpa-nightly.yml
|
|
job: jpa-full-matrix
|
|
execution: job
|
|
- id: jpa-failure-injection
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: jpa-failure-injection
|
|
workflow: jpa-nightly.yml
|
|
job: jpa-failure-injection
|
|
execution: job
|
|
- id: jpa-query-plan-and-security
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: jpa-query-plan-and-security
|
|
workflow: jpa-nightly.yml
|
|
job: jpa-query-plan-and-security
|
|
execution: job
|
|
- id: jpa-pool-pressure
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: jpa-pool-pressure
|
|
workflow: jpa-nightly.yml
|
|
job: jpa-pool-pressure
|
|
execution: job
|
|
#
|
|
# jpa-next-*.yml — weekly early-warning lanes against unreleased upstream versions. They exist to
|
|
# find out before the upgrade, so they block nothing.
|
|
#
|
|
# Weekly early-warning lanes against unreleased upstream versions. They exist to find out
|
|
# before the upgrade, so they block nothing.
|
|
- id: hibernate8-compatibility
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: hibernate8-compatibility
|
|
workflow: jpa-next-hibernate8.yml
|
|
job: hibernate8-compatibility
|
|
execution: job
|
|
- id: jpa4-compatibility
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: jpa4-compatibility
|
|
workflow: jpa-next-jpa4.yml
|
|
job: jpa4-compatibility
|
|
execution: job
|
|
- id: postgresql19-compatibility
|
|
release_blocking: false
|
|
mechanism: workflow-job
|
|
ref: postgresql19-compatibility
|
|
workflow: jpa-next-postgresql19.yml
|
|
job: postgresql19-compatibility
|
|
execution: job
|
|
#
|
|
# jpa-r2-evidence.yml — dispatch-only production-profile manifest run.
|
|
- 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
|
|
|
|
# =================================================================================
|
|
# STAGE 3 — release tag. These stop a release.
|
|
# =================================================================================
|
|
#
|
|
# release.yml — one workflow, one deployable unit. Evidence jobs first, then app-image-release,
|
|
# which `needs:` all of them: while the image build lived in its own workflow it could publish
|
|
# while a sibling suite was still running or already red.
|
|
- id: httpclient-stable-contract
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: httpClientStableContractTest
|
|
workflow: release.yml
|
|
job: httpclient-release-gate
|
|
execution: explicit
|
|
- id: httpclient-security-suite
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: httpClientSecurityTest
|
|
workflow: release.yml
|
|
job: httpclient-release-gate
|
|
execution: explicit
|
|
- id: httpclient-fault-injection
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: httpClientFailureInjectionTest
|
|
workflow: release.yml
|
|
job: httpclient-release-gate
|
|
execution: explicit
|
|
- id: httpclient-performance-certification
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: httpClientPerformanceTest
|
|
workflow: release.yml
|
|
job: httpclient-release-gate
|
|
execution: explicit
|
|
- id: httpclient-spring62-api-surface
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: spring62ApiSurfaceScan
|
|
workflow: release.yml
|
|
job: httpclient-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: release.yml
|
|
job: httpclient-release-gate
|
|
execution: job
|
|
- id: httpclient-spring70-compatibility
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: spring70CompatibilityTest
|
|
workflow: release.yml
|
|
job: httpclient-release-gate
|
|
execution: explicit
|
|
- id: httpclient-documentation-drift
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: httpclient-documentation
|
|
workflow: release.yml
|
|
job: httpclient-documentation
|
|
execution: job
|
|
- id: httpclient-event-loop-blocking
|
|
release_blocking: true
|
|
mechanism: gradle-custom-task
|
|
ref: httpClientBlockHoundTest
|
|
workflow: release.yml
|
|
job: httpclient-release-gate
|
|
execution: explicit
|
|
#
|
|
# Tag-triggered (`v*`, `web-v*`). The Stable web release gate.
|
|
- id: web-stable-release-gate
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: web-stable-release-gate
|
|
workflow: release.yml
|
|
job: web-stable-release-gate
|
|
execution: job
|
|
#
|
|
# Tag-triggered (`v*`, `websocket-v*`). The Stable websocket release gate.
|
|
- id: websocket-stable-release-gate
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: websocket-stable-release-gate
|
|
workflow: release.yml
|
|
job: websocket-stable-release-gate
|
|
execution: job
|
|
#
|
|
# 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,
|
|
# generates its CycloneDX SBOM, refuses to publish on a CRITICAL or HIGH finding, and only then
|
|
# pushes the semver and sha- tags and records the digest. The scan is a step inside that control
|
|
# rather than a control of its own, which is the same shape filesystem-vulnerability-scan uses for
|
|
# the trivy-fs job. `release_blocking: true` holds because the workflow runs only for a release
|
|
# tag, so the job failing fails that release run.
|
|
- id: container-image-release
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: app-image-release
|
|
workflow: release.yml
|
|
job: app-image-release
|
|
execution: job
|
|
#
|
|
# The join this repository cannot express, stated rather than implied.
|
|
#
|
|
# Most of it is closed now. app-image-release lives in release.yml and `needs:` the four evidence
|
|
# jobs in that file, so the image cannot be built, scanned or pushed past a failed web, websocket,
|
|
# httpclient or architecture gate. `needs:` still reaches only inside one workflow file, and two
|
|
# evidence workflows are still outside it: jpa-release.yml and fileserver-release.yml both answer
|
|
# the same `v*` tag and neither can be waited on from here.
|
|
#
|
|
# Those two are not in release.yml for a mechanical reason rather than a design one —
|
|
# JpaReleaseRenderingTest reads `.github/workflows/jpa-release.yml` by that exact path, and
|
|
# FileserverDocumentationCoverageTest requires the job ids docs/fileserver/support-matrix.md names
|
|
# to be defined in a `.github/workflows/fileserver-*.yml`. Folding them in means changing a test
|
|
# and a document in src/ and docs/ in the same commit, which is a decision with owners rather than
|
|
# a wiring oversight.
|
|
#
|
|
# Until then the remaining control is a human one: the GitOps repository promotes a digest whose
|
|
# tag's jpa-release and fileserver-release runs somebody has looked at, not a digest that merely
|
|
# exists. Registered here so that sentence lives somewhere a lint can point at, the way
|
|
# fileserver-pvc-cluster-certification does for the storage claim.
|
|
- id: container-release-evidence-join
|
|
release_blocking: conditional
|
|
mechanism: delegated-pending
|
|
ref: container-release-evidence-join
|
|
workflow: release.yml
|
|
job: app-image-release
|
|
execution: job
|
|
#
|
|
# verifyCleanArchitectureDependencies, verifyPublicPathSnapshot, verifyEnvKeys and the
|
|
# bootstrap architecture suite, once per release. The four release workflows this replaced ran
|
|
# the dependency gate six times and the architecture suite four times for one tag, on separate
|
|
# runners, against one commit.
|
|
- id: release-architecture-and-surface
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: architecture-and-surface
|
|
workflow: release.yml
|
|
job: architecture-and-surface
|
|
execution: job
|
|
#
|
|
# jpa-release.yml — tag-triggered (`v*`). jpa-release-promotion needs jpa-release-gate, so the
|
|
# promotion cannot run past a failed gate. Its own file because JpaReleaseRenderingTest reads that
|
|
# exact path and holds its matrix to src/config/jpa/release-registry.json.
|
|
#
|
|
# Tag-triggered (`v*`). jpa-release-promotion needs jpa-release-gate, so the promotion cannot
|
|
# run past a failed gate.
|
|
- id: jpa-release-gate
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: jpa-release-gate
|
|
workflow: jpa-release.yml
|
|
job: jpa-release-gate
|
|
execution: job
|
|
- id: jpa-release-promotion
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: jpa-release-promotion
|
|
workflow: jpa-release.yml
|
|
job: jpa-release-promotion
|
|
execution: job
|
|
- id: jpa-architecture-and-docs
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: jpa-architecture-and-docs
|
|
workflow: jpa-release.yml
|
|
job: jpa-architecture-and-docs
|
|
execution: job
|
|
#
|
|
# fileserver-release.yml — tag-triggered (`v*`). Its own file for the same reason fileserver-pr.yml
|
|
# is.
|
|
#
|
|
# Tag-triggered: `v*` runs this workflow and a failing job fails that
|
|
# release. fileserver-pvc-certification checks the manifest only — the cluster half is the
|
|
# delegated-pending row below it.
|
|
- id: fileserver-full-verification
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: fileserver-full-verification
|
|
workflow: fileserver-release.yml
|
|
job: fileserver-full-verification
|
|
execution: job
|
|
- id: fileserver-documentation-gate
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: fileserver-documentation-gate
|
|
workflow: fileserver-release.yml
|
|
job: fileserver-documentation-gate
|
|
execution: job
|
|
- id: fileserver-pvc-certification
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: fileserver-pvc-certification
|
|
workflow: fileserver-release.yml
|
|
job: fileserver-pvc-certification
|
|
execution: job
|
|
#
|
|
# The other half of the PVC claim, and the half no runner can produce. An operator
|
|
# applies infra/fileserver/kubernetes/pvc-certification-job.yaml to a real cluster and
|
|
# records the result in docs/fileserver/storage-certification.md. The workflow step
|
|
# that pretended to do this in CI reported success whenever the cluster secret was
|
|
# absent, which is every checkout of this template; it is gone, and the claim is
|
|
# tracked here instead of being green for nothing.
|
|
- id: fileserver-pvc-cluster-certification
|
|
release_blocking: conditional
|
|
mechanism: delegated-pending
|
|
ref: fileserver-pvc-cluster-lane
|
|
workflow: fileserver-release.yml
|
|
job: fileserver-pvc-certification
|
|
execution: job
|
|
- id: fileserver-sensitive-telemetry-scan
|
|
release_blocking: true
|
|
mechanism: workflow-job
|
|
ref: fileserver-sensitive-telemetry-scan
|
|
workflow: fileserver-release.yml
|
|
job: fileserver-sensitive-telemetry-scan
|
|
execution: job
|