Files
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
..

build-logic

The included build that holds this repository's Gradle conventions. A convention lives here when the same machine code was otherwise copied into more than one place, and the copies could drift apart without any check noticing.

What is here

Plugin Owns
ca.architecture-registry (settings) project inclusion, directory mapping, and the parsed registry every other reader shares
ca.strict-test-lane tagged / named / own-source-set Test lanes that cannot pass without executing something
ca.strict-qualification qualification lanes that cannot pass without executing every named class, re-checked against the JUnit XML
ca.evidence the JUnit XML reader and the no-skip / required-class rules built on it
ca.api-surface read-only API surface verification with an explicit, separate update task
ca.dependency-policy declared absences, checked against the resolved graph rather than against a comment
ca.runtime-membership the resolved runtime project closure against the registry's memberships
ca.platform-module what a vendored platform leaf (messaging:*, grpc:*, grpc-advanced:*) is: java-library
ca.grpc-platform-module ca.platform-module plus the module-scope io.grpc:grpc-bom import, for the leaves that already had it

ca.testkit-publisher was here and is not any more: every leaf with a testkit source set moved to Gradle's own java-test-fixtures (ADR-BUILD-001).

Plain classes rather than plugins, because settings and projects load plugins through different mechanisms and more than one caller needs each:

Class Owns
dev.caskeleton.buildlogic.ModuleRegistry reading and validating config/architecture/modules.json, including which runtime compositions exist
dev.caskeleton.buildlogic.JUnitEvidence one JUnit XML reader, with DOCTYPE processing off and no defaulting of absent counts
dev.caskeleton.buildlogic.RequiredTestExecution the single decision "a test this build names must actually have run", asked by ca.strict-test-lane and by ca.evidence
dev.caskeleton.buildlogic.JavaPublicTypes public top-level types, parsed by javac rather than matched with a regular expression

What the design named and this build does not have

The remediation design's §10.2 listed eight conventions. Two of them were attempted or assessed and deliberately not built, and the reasons belong next to the code rather than in a review thread.

ca.java-leaf — reverted

Written, measured, reverted. The full reasoning is in src/build.gradle beside the static-analysis block it would have moved. In short: a recorded decision (feature-static-analysis-quality-contract D8) already put that baseline in the root subprojects {} block; the block is applied once and copied nowhere, so extracting it removes no duplication; and build-logic would have to re-declare the spotless / spotbugs / errorprone / dependency-management coordinates and their versions, which creates a drift surface where there was none.

A content-level baseline of the resolved analysis configuration — compiler args, encoding, release, Checkstyle tool version and config, SpotBugs effort and report level, for every source set of every project — was captured before the attempt and compared after the revert, because the task graph cannot see a weakened Error Prone flag: the task names are identical either way. The two dumps are identical.

ca.optional-adapter — not warranted

Its stated responsibility was "activation metadata and disabled/on composition contract wiring". Neither half is build machine code in this repository:

  • Activation metadata is one registry, docs/registries/env-keys.yaml, verified by one root task, verifyEnvKeys. There is no per-leaf copy for a convention to deduplicate.
  • The off invariant and the on fail-closed contract are ordinary tests over the shared test source set, gated by the master switch in @ConditionalOnProperty at runtime. They need no source set, no configuration, and no task of their own.

A plugin here would have to invent state to hold — an optionalAdapter { switch = '...' } block that no build step reads — and a declaration nothing checks is worse than no declaration, because it reads like a guarantee.

Testing a convention

cd src
./gradlew -p build-logic test --console=plain

The lane and registry conventions are covered by Gradle TestKit against real builds rather than by reading the plugin source, because the properties that matter — a lane that discovers nothing fails, a lane never reports up-to-date, a malformed registry is refused before any project is included — are runtime behaviour rather than text in a script. Two of those tests exist because the obvious reading of the Gradle documentation was wrong: failOnNoDiscoveredTests does not fire when a tag filter matches nothing, and failOnNoMatchingTests does not fire when only some of the named tests are missing.