Files
clean-architecture-backend-…/docs/superpowers/specs/2026-09-16-verification-surface-reduction-design.md
DongHyeonkaandClaude Opus 5 ef947e5bb0 refactor(build,ci): 현재 상태 검증을 걷어내고 불변조건만 남기는 검증 표면 축소
외부 리뷰("현재 상태를 유지하기 위한 검증이 너무 많고, 그 검증 자체를
다시 검증하는 구조까지 생겼다")를 설계 문서로 정리하고 코드로 반영한다.
설계·판단 근거는 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>
2026-09-16 20:33:19 +09:00

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.ymlkubectl 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. 채택하는 판단 기준

리뷰의 핵심 원칙을 이 저장소의 결정 규칙으로 승격한다.

  1. 현재 상태(Current State)가 아니라 불변조건(Invariant)을 검증한다. "모듈이 N개다", "필드가 정확히 이 집합이다", "문서에 적힌 수가 레지스트리와 같다"는 현재 상태다. "ID가 중복되지 않는다", "domain이 framework를 참조하지 않는다"는 불변조건이다.
  2. 검증기를 검증하지 않는다. validator를 mutation해서 validator가 실패하는지 보는 task는 스켈레톤의 기본 빌드 정책이 아니다.
  3. 자동으로 구성할 수 있는 것은 검증으로 강제하지 않는다. convention plugin으로 주입한다.
  4. 로컬 check는 로컬이어야 한다. leaf의 check는 그 leaf만 검사한다.
  5. 릴리스 불변조건을 일반 개발 빌드에 강제하지 않는다.
  6. 문서 drift는 빌드 실패 사유가 아니다. 커스텀 Markdown/Java 파서를 유지하지 않는다.
  7. GitHub Actions = CI + artifact 생산, Argo CD = CD. CI에 클러스터 배포 자격증명을 넣지 않는다.
  8. 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.gradleconfigure(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. 명시적 위험

  1. leaf 62개에 plugins {} 블록을 추가한다. 적용 순서가 바뀌므로 leaf별 check로 확인한다.
  2. dependency locking이 STRICT라, 어떤 leaf의 configuration에 의존성이 추가되면 락 파일이 깨진다. 따라서 convention 이동은 해석되는 의존성 집합을 바꾸지 않는 범위로 제한한다. ca.spring-config는 이미 processor를 선언한 leaf만 opt-in한다.
  3. 삭제하는 task 이름을 참조하는 workflow/문서/테스트를 같은 변경에서 고친다.