외부 리뷰("현재 상태를 유지하기 위한 검증이 너무 많고, 그 검증 자체를
다시 검증하는 구조까지 생겼다")를 설계 문서로 정리하고 코드로 반영한다.
설계·판단 근거는 docs/superpowers/specs/2026-09-16-verification-surface-reduction-design.md.
삭제
- .github/ci-gate-matrix.yml(1,025줄) + verify-gate-matrix.sh(568줄):
Gradle task graph와 workflow graph에 이미 있는 정보의 3중 복제
- verify-gradle-wrapper.sh(799줄): workflow 바이트 해시 잠금.
wrapper 검증은 gradle/actions/wrapper-validation(full SHA 핀)에 위임
- DeveloperExperienceContractTest 등의 CI YAML mutation 테스트:
애플리케이션 test suite가 GitHub Actions YAML 파서를 검증하던 계층 역전
- 문서 drift 파서: verifyReadmeCommands, verifyRunbookReferences,
verifyDocumentedLeafCount, verifyTestSourceSetRegistry
- 빈 레지스트리를 지키던 커스텀 YAML 파서: verifyTrivyignore,
verifyQuarantineSunset, flaky-quarantine.yaml
- verifyConfigurationPropertiesProcessor, verifyOneTypePerFile:
각각 ca.spring-config convention과 Checkstyle OneTopLevelClass가 대체
- 정상 입력으로도 성공할 수 없던 messaging always-fail task
- ModuleRegistry의 JSON 필드 집합 정확 일치, sample-portfolio negative guard
이동
- java/quality/spring 공통 설정을 configure(subprojects) 블록에서
ca.java-conventions / ca.quality-conventions / ca.java-library /
ca.spring-library convention plugin으로
- 아키텍처 검증을 ca.architecture로, JPA·messaging qualification을
gradle/qualification/ 아래로, verifyEnvKeys를 :app-bootstrap 소유로
완화
- Git revision은 releaseCheck·아카이브 생성에서만 요구. 일반 빌드는 SNAPSHOT
- SpotBugs/FindSecBugs는 로컬 check에서 빼고 qualityCheck 레인으로
task 계층
- leaf check는 그 leaf만. architectureCheck / qualityCheck /
configContractCheck / integrationCheck / ci / releaseCheck로 이름 분리
CI
- _reusable-gradle.yml 신규. checkout + wrapper validation + JDK/캐시 공통화
- fileserver-release.yml -> fileserver-certification.yml (CD가 아니라 certification)
- GitHub Actions = CI + artifact, Argo CD = CD 경계를 docs/ci-cd/boundary.md로 고정
순증감 +3,274 / -7,483.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 KiB
검증 표면 축소 설계 — 스켈레톤을 qualification framework에서 되돌리기
날짜: 2026-09-16 근거: 외부 리뷰 "현재 상태를 유지하기 위한 검증이 너무 많고, 그 검증 자체를 다시 검증하는 구조까지 생겼다"
0. 리뷰 기준점과 현재 체크아웃의 차이
리뷰는 이 저장소의 이전 스냅샷을 보고 작성됐다. 실제 작업 전에 항목별로 재측정했고, 이미 해결된 항목은 "완료"로 확정하고 남은 항목만 작업 대상으로 삼는다.
| 리뷰 주장 | 리뷰가 본 값 | 현재 실측 | 판정 |
|---|---|---|---|
settings.gradle 183줄 validator |
183줄 | 16줄 (ca.architecture-registry 설정 플러그인으로 이전) |
완료 |
| 모듈 수 정확히 18개 강제 | 있음 | 없음 | 완료 |
runtime composition이 정확히 app-bootstrap |
있음 | runtime_compositions를 JSON에서 읽음 |
완료 |
| JSON 필드 집합 정확히 일치 | 있음 | ModuleRegistry.groovy:88,124에 그대로 있음 |
작업 대상 |
sample-portfolio negative re-entry guard |
있음 | ModuleRegistry.groovy:215에 그대로 있음 |
작업 대상 |
build-logic 없음 |
없음 | 존재 (9개 convention plugin) | 부분 완료 |
| version catalog 없음 | 없음 | gradle/libs.versions.toml 140줄 |
완료 |
adapter/inbound/web/build.gradle 799줄 OpenAPI |
799줄 | 256줄, codegen 없음 | 완료 |
leaf check가 저장소 전체 검사 |
그랬음 | 루트 check로 이미 이전 |
부분 완료 |
fileserver-release.yml의 kubectl apply |
있음 | 이미 제거됨 | 완료 |
httpclient-release.yml |
있음 | 파일 자체가 없음 | 해당 없음 |
ci-gate-matrix.yml 282줄 / 37 gate |
282줄 | 1,025줄 / 107 gate | 작업 대상(악화) |
verify-gate-matrix.sh |
있음 | 568줄 | 작업 대상 |
verify-gradle-wrapper.sh 740줄 |
740줄 | 799줄 | 작업 대상 |
DeveloperExperienceContractTest 1,100줄 |
1,100줄 | 1,141줄 (CI YAML mutation test 25개) | 작업 대상 |
src/build.gradle 2,469줄 |
2,469줄 | 3,211줄 | 작업 대상(악화) |
| always-fail Messaging task | 있음 | 9개 그대로 | 작업 대상 |
| 모든 빌드에 Git SHA 강제 | 있음 | 그대로 (build.gradle:47) |
작업 대상 |
1. 채택하는 판단 기준
리뷰의 핵심 원칙을 이 저장소의 결정 규칙으로 승격한다.
- 현재 상태(Current State)가 아니라 불변조건(Invariant)을 검증한다. "모듈이 N개다", "필드가 정확히 이 집합이다", "문서에 적힌 수가 레지스트리와 같다"는 현재 상태다. "ID가 중복되지 않는다", "domain이 framework를 참조하지 않는다"는 불변조건이다.
- 검증기를 검증하지 않는다. validator를 mutation해서 validator가 실패하는지 보는 task는 스켈레톤의 기본 빌드 정책이 아니다.
- 자동으로 구성할 수 있는 것은 검증으로 강제하지 않는다. convention plugin으로 주입한다.
- 로컬
check는 로컬이어야 한다. leaf의check는 그 leaf만 검사한다. - 릴리스 불변조건을 일반 개발 빌드에 강제하지 않는다.
- 문서 drift는 빌드 실패 사유가 아니다. 커스텀 Markdown/Java 파서를 유지하지 않는다.
- GitHub Actions = CI + artifact 생산, Argo CD = CD. CI에 클러스터 배포 자격증명을 넣지 않는다.
- Template maintainer용 검증과 Template consumer용 검증을 분리한다.
이 기준은 기존의 D8 결정("quality 블록을 convention plugin으로 빼지 않는다")을 대체한다.
D8의 3번 근거(build-logic이 플러그인 버전을 두 번 선언하게 된다)는 이미 무효다 —
build-logic/settings.gradle이 메인 빌드의 libs.versions.toml을 읽고 있으므로 버전은 한 곳에 있다.
2. 목표 task 계층
:<leaf>:check 컴파일 + 단위 테스트 + spotless + checkstyle + errorprone (그 leaf만)
check (root) 모든 leaf의 check
architectureCheck 의존 방향 · 런타임 멤버십 · application-core 순수성 · Git 미추적 패키지
qualityCheck SpotBugs + FindSecBugs (전 leaf)
configContractCheck :app-bootstrap:verifyEnvKeys
integrationCheck 통합/슬라이스 레인
ci check + architectureCheck + qualityCheck + configContractCheck
releaseCheck ci + 아카이브 위생 + public path snapshot + 릴리스 provenance
qualification(JPA readiness, Messaging evidence, notification evidence, transport 등)은
어느 것도 check / ci에 걸지 않는다. 명시적으로 이름을 불러야 실행된다.
3. 변경 목록
3.1 삭제
| 대상 | 줄 수 | 이유 |
|---|---|---|
.github/ci-gate-matrix.yml |
1,025 | Gradle task graph와 workflow graph에 이미 있는 정보의 3중 복제 |
.github/scripts/verify-gate-matrix.sh |
568 | 위 복제본의 정합성 검사기 |
.github/scripts/verify-gradle-wrapper.sh |
799 | workflow 바이트 해시 잠금. 공격자는 해시도 같이 고치면 되고, 개발자는 주석 하나에 해시를 갱신해야 한다 |
DeveloperExperienceContractTest의 wrapper/gate mutation test 25개 |
~700 | 애플리케이션 test suite가 GitHub Actions YAML 파서를 검증 |
| always-fail Messaging skeleton task 9개 | ~85 | 정상 입력으로도 성공할 수 없는 task. TODO를 Gradle API로 만든 것 |
verifyReadmeCommands |
105 | 커스텀 Markdown 명령 파서 |
verifyRunbookReferences |
75 | 커스텀 runbook 식별자 파서 |
verifyDocumentedLeafCount |
78 | 문서에 적힌 leaf 수 = 전형적인 현재 상태 검증 |
verifyTestSourceSetRegistry |
92 | 문서 표 ↔ source set 대조 파서 |
verifySpotBugsAnalysisFailureContract |
58 | 검증기의 검증 |
verifyConfigurationPropertiesProcessor |
90 | build.gradle을 regex로 읽는 검증 → convention으로 대체 |
verifyOneTypePerFile |
8 | 이미 checkstyleMain 별칭. 호출자를 checkstyleMain으로 바꾸고 이름 폐기 |
verifyTrivyignore |
105 | 빈 registry를 지키는 커스텀 YAML 파서 |
verifyQuarantineSunset |
250 | 빈 registry를 지키는 커스텀 YAML + Java 파서 |
blankJavaCommentsAndLiterals |
95 | 위 두 개만 쓰던 Java 렉서 흉내 |
ModuleRegistry의 필드 집합 정확 일치 · sample-portfolio negative guard |
~25 | 확장 차단 · 삭제된 모듈의 역사가 영구 invariant |
합계 약 4,150줄.
3.2 이동
| 대상 | 현 위치 | 새 위치 | 이유 |
|---|---|---|---|
| java/quality/spring 공통 설정 | build.gradle의 configure(subprojects…) |
ca.java-conventions · ca.quality-conventions · ca.java-library · ca.spring-library |
모듈이 자신의 성격을 스스로 선언 |
verifyCleanArchitectureDependencies 외 3개 |
build.gradle |
ca.architecture |
아키텍처 규칙을 한 곳에 |
| JPA readiness registry + release gate | build.gradle ~610줄 |
gradle/qualification/jpa-qualification.gradle |
빌드 정책과 certification 분리 |
| Messaging evidence manifest | build.gradle ~600줄 |
gradle/qualification/messaging-qualification.gradle |
동일 |
verifyEnvKeys |
루트 task, 루트 check |
:app-bootstrap 소유, configContractCheck |
환경 계약은 composition root의 책임 |
| 모듈 의존 edge 존재/자기참조 검사 | settings 단계(ModuleRegistry) |
verifyCleanArchitectureDependencies |
settings에서 죽으면 복구 수단이 없다 |
3.3 완화
| 대상 | 현재 | 변경 후 |
|---|---|---|
| Git revision | 없으면 모든 빌드가 configuration 단계에서 실패 | 일반 빌드는 0.0.1-SNAPSHOT/unknown. releaseCheck·아카이브 생성에서만 요구 |
| SpotBugs / FindSecBugs | 전 leaf check 블로킹 |
qualityCheck (CI lane). 로컬 check에서 제외 |
.trivyignore.yaml |
커스텀 파서가 expiry/reason 강제 | 파일은 유지, 규칙은 문서화 + CODEOWNERS 승인 |
flaky-quarantine.yaml |
커스텀 파서 + 14일 sunset 강제 | 레지스트리 삭제. @Tag("quarantine") 제외와 quarantineTest는 유지(각 3줄) |
3.4 CI
.github/workflows/
├── _reusable-gradle.yml 신규 — checkout + wrapper validation + JDK/캐시 + Gradle 호출
├── ci-quality-gates.yml → 재사용 workflow 호출로 축약
├── dependency-vulnerability.yml 유지 (dependency-review + submission + Trivy)
├── link-check.yml 유지
├── release.yml image/SBOM 생산까지. 클러스터 배포 없음
├── fileserver-certification.yml ← fileserver-release.yml 개명 (CD가 아니라 certification)
└── 나머지 feature qualification 유지, 전부 재사용 workflow 사용
wrapper 검증은 gradle/actions/wrapper-validation(full SHA 핀)에 맡기고, 재사용 workflow
한 곳에서만 선언한다. full SHA 핀은 리뷰 판단대로 유지한다.
3.5 CI/CD 경계
GitHub Actions ──► test / scan / image build / SBOM / push ──► GitOps repo manifest ──► Argo CD ──► K8s
docs/ci-cd/boundary.md로 고정한다. GitHub Actions는 kubectl apply / helm upgrade /
argocd app sync를 하지 않는다. Argo CD auto-sync를 쓰면 CI에 클러스터 자격증명이 필요 없다.
3.6 Template maintainer vs consumer
docs/ci-cd/template-vs-consumer.md로 구분을 명시한다.
- Template CI: sample 모듈 제거 가능성, optional 모듈 조합 빌드, 레지스트리 확장 가능성
- Consumer CI: 자기 애플리케이션의 test / architecture / security / release
파생 프로젝트가 가져가면 안 되는 workflow와 task를 목록으로 적는다.
4. 유지하는 것 (리뷰가 "잘한 것"으로 분류)
verifyCleanArchitectureDependencies, dependency locking(STRICT), full SHA action 핀,
dependency-review, Trivy 스캔, path filter 기반 feature CI, nightly 분리, Spotless,
-Werror/-Xlint, ErrorProne, 재현 가능한 아카이브.
5. 검증 방법
./gradlew help --offline로 configuration 성공- 변경한 leaf마다
./gradlew <path>:check --offline ./gradlew architectureCheck --offline- 워크플로 YAML은
python3 -c "import yaml…"로 파싱 확인 - 삭제한 task 이름이 저장소 어디에도 남지 않았는지
grep
6. 명시적 위험
- leaf 62개에
plugins {}블록을 추가한다. 적용 순서가 바뀌므로 leaf별check로 확인한다. - dependency locking이 STRICT라, 어떤 leaf의 configuration에 의존성이 추가되면 락 파일이 깨진다.
따라서 convention 이동은 해석되는 의존성 집합을 바꾸지 않는 범위로 제한한다.
ca.spring-config는 이미 processor를 선언한 leaf만 opt-in한다. - 삭제하는 task 이름을 참조하는 workflow/문서/테스트를 같은 변경에서 고친다.