Files
llm-wiki/raw/branch-notes/feature-build-release-supply-chain-contract.md
T

504 lines
75 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: branch / feature-build-release-supply-chain-contract
source_type: branch-note
status: raw
branch: feature-build-release-supply-chain-contract
parent_branch:
related_projects: [ca-skeleton]
governing_docs: [wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]
tags: [branch, ca-skeleton, ci-cd, gradle, docker, supply-chain]
created: 2026-05-22
updated: 2026-06-23
target_merge:
status_label: review
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-029
kind: project-work-item
project: ca-skeleton-operational-contract
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-029
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-BUILD-001@1]
refines: []
overrides: []
depends_on: []
contract_packet: 1
contract_packet_sha256: 2307d3faa4febc43cbbe5f18fae9ae683e96a7b2b9f0fbe86065acf1a767a65b
---
# branch: feature-build-release-supply-chain-contract
> Layer: `raw/branch-notes/` — artifact, dependency, image, vulnerability, rollback 기준을 정의합니다.
<!-- section-id: branch-parent -->
## 부모 (필수)
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: Gradle release·SBOM·signature artifact가 생성된다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-BUILD-001@1` | build tool은 Gradle Groovy DSL이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
운영 가능한 skeleton은 실행되는 코드만이 아니라 배포 가능한 artifact를 안정적으로 만들어야 합니다. dependency drift, 취약 이미지, rollback 불가 artifact는 도메인과 무관하게 실무 장애가 됩니다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- dependency version locking.
- artifact versioning.
- container image base 기준.
- non-root runtime 기준.
- SBOM 생성 기준.
- vulnerability severity별 release block 기준.
- rollback 기준.
### 제외 범위
- 특정 registry 운영.
- 조직별 release approval workflow.
- cloud provider 배포 스크립트.
## 근거 (필수, 최소 1개+)
> 본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [[raw/official-docs/supply-chain-cosign-keyless-sigstore]] | Sigstore Cosign keyless + Fulcio + Rekor (ca-tmpl baseline |
| [[raw/official-docs/supply-chain-slsa-provenance-framework]] | SLSA v1 |
| [[raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking]] | Gradle dependency-locking vs Maven Enforcer |
| [[raw/official-docs/cosign-keyless-identity-verification-policy]] | 참조 |
| [[raw/official-docs/slsa-v1-provenance-schema]] | 참조 |
| [[raw/official-docs/vuln-severity-cvss-v31-spec-first-official]] | D2 — high/critical release-blocking 기준: CVSS v3.1 §5 severity bands (C1), optional 선언 (C2), Base Score intrinsic/worst-case 정의 (C3) |
| [[raw/official-docs/semver-2-0-0-spec-semver-official]] | D9 — artifact version = SemVer + git sha suffix (1.2.3+a1b2c3d): build metadata(`+` suffix)는 precedence에서 무시됨 (SEMVER-C3, SEMVER-C4) |
| [[raw/official-docs/trivy-severity-exit-code-gating]] | D2 — severity→release-block 정책의 집행(enforcement) 메커니즘: Trivy `--exit-code 1 --severity HIGH,CRITICAL` 기본 패턴의 공식 출처 (TRIVY-EG-C1~C3) |
| [[raw/official-docs/renovate-gradle-manager-official]] | D3 — Renovate Gradle 지원 범위(파일 패턴, --write-locks lockfile 갱신, Version Catalog), self-hosted `allowedUnsafeExecutions: ["gradleWrapper"]` supply-chain 보안 제약 (RENOV-GRAD-C1~C4) |
| [[raw/official-docs/gradle-reproducible-archives-working-with-files]] | D10 — `preserveFileTimestamps=false` / `reproducibleFileOrder=true` 의 Gradle 공식 API 명세 + `tasks.withType<AbstractArchiveTask>().configureEach {}` 전역 적용 패턴 (GRADLE-RA-C1~C3) |
| [[raw/official-docs/dependabot-supported-ecosystems-official]] | D3 — Dependabot Gradle ecosystem 공식 지원 범위: version updates ✓ / security updates ✓(단 dependency submission API 수동 업로드 한정) / Private registries ✓ / Vendoring ✗; 파일 파싱 방식(Gradle 미실행) 공식 확인 (DBOT-ECO-C1~C5) |
| [[raw/official-docs/calver-spec-calver-official]] | D9 negative-evidence — CalVer when-to-use 기준(대규모/상시변동 scope, 시간민감)이 library/skeleton에 미해당함을 원문 부재로 뒷받침 (CALVER-C2, C3, C5) |
| [[raw/official-docs/reproducible-builds-org-jvm-guide]] | D10 — reproducible builds 공식 정의(cross-ecosystem) + JVM nondeterminism 원인(timestamps/file ordering/locale/umask) + Gradle `isPreserveFileTimestamps=false` + `isReproducibleFileOrder=true` 두 설정이 두 주요 원인 제거 근거 (RB-JVM-C1~C6) |
## 외부 근거 / 대안 조사 (2026-05-22 — Group G-E: Build / Release / Supply Chain)
본 branch의 Cosign keyless + SLSA provenance + Gradle dependency-locking + SemVer+sha + reproducibility 결정에 대한 외부 source.
- **채택 결정 (Cosign keyless + SLSA + Gradle lock)**:
- [[raw/official-docs/supply-chain-cosign-keyless-sigstore]] — Sigstore Cosign keyless + Fulcio + Rekor (ca-tmpl baseline)
- [[raw/official-docs/supply-chain-slsa-provenance-framework]] — SLSA v1.0 build levels + in-toto attestation
- [[raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking]] — Gradle dependency-locking vs Maven Enforcer
- **검토한 대안**:
- **대안 1: GPG signing (legacy)** — Cosign 이전 표준
- **대안 2: Notary v1 (Docker Content Trust)** — Cosign으로 대체된 deprecated 경로
- **대안 3: in-toto attestations** — SLSA에 통합되어 별도 도구로는 미채택
- **대안 4: JFrog Artifactory provenance** — vendor 통합 솔루션
- **비교 핵심**: Cosign keyless가 GPG signing 대비 키 관리 부담 제거(Fulcio가 ephemeral cert 발급, Rekor가 transparency log). SLSA Build L3 도달은 hermetic build 필요. Maven에는 1급 lockfile 부재(Enforcer는 부분 대응) — Gradle 선택 근거. **보강 후보**: Cosign signature 누락만 차단으로 부족 — `--certificate-identity` + `--certificate-oidc-issuer` identity 매칭 정책 추가 필요. SLSA v1.0 spec 실제 필드명(`buildDefinition.externalParameters` 등)과 ca-tmpl 약식 매핑 정정 필요.
- **후속 보강 (2026-05-22)**: Cosign signature 존재 검증만으로는 불충분. identity 매칭 정책 추가 필요. [[raw/official-docs/cosign-keyless-identity-verification-policy]] 참조.
- **후속 보강 (2026-05-22)**: SLSA v1.0 spec 실제 필드명과 약식 매핑 정정 필요. [[raw/official-docs/slsa-v1-provenance-schema]] 참조.
- **자동조사 라운드 (2026-06-15 — `/branch-spec`)**: UNSUPPORTED 였던 D2(vuln severity)·D3(dependency bot)·D9(SemVer)·D10(reproducibility) 에 공식 source 8건 아카이브. D9·D10 은 본 branch 소유 영역(artifact versioning·reproducibility) → official-standard/vendor-doc 로 승급. D2·D3 은 _정책 single-owner_ 가 [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] 이므로 본 branch 는 _consume_ 관계 — §Audit & Findings `OWNER_RECONCILE` 참조.
## TODO
> TODO drained 2026-05-22 — dependency lock/update, artifact version naming, container base/non-root runtime, SBOM 생성, vulnerability severity 차단, rollback artifact 보관 정책 모두 "결정 사항" / "Supply Chain Defaults" / "테스트 계약"에 반영됨. 잔존 TODO 없음.
## 진행 중 메모
- 2026-06-15 `/branch-spec` 자동조사: 근거 없던 결정 5개(D2/D3/D9/D10/D11) 중 D2/D3/D9/D10 을 공식 source 로 보강(§Sources 하단 8행). D11(rollback 10/90 retention)은 외부 표준 부재 → `UNSUPPORTED_DECISION` 유지.
- D2/D3 은 evidence 가 붙었으나 _정책 owner_ 는 별도 branch — 자동 rewrite 하지 않고 §Audit & Findings 에 정합 권고로만 기록(사용자 결정 영역). `/sync` 가 Single-Owner 정합 수행 예정.
- 2026-06-20~21 Phase C2 구현 완료. Gradle strict lock, 재현 가능한 archive, traceable version, digest-first image release, SBOM, Cosign keyless, SLSA provenance, High/Critical 차단, rollback retention audit를 코드와 계약 테스트로 배선했다.
- GitHub-hosted OIDC/Rekor/GHCR와 실제 release 생성은 로컬에서 재현할 수 없어 `needs-confirmation`; 구현·로컬 검증과 운영 검증 경계를 아래 §구현 결과에 분리했다.
## 구현 결과
> 아래 §구현 가이드의 2026-06-15 `planned` 표시는 구현 전 설계 스냅샷이다. 현재 상태 SSOT는 이 절이며, 실제 코드·테스트가 존재하는 항목만 `actually-implemented` 또는 `locally-verified`로 분류한다.
| Decision | 구현 상태 | 구현 증거 | 검증 등급 |
| ------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| D1 · D9 | `src/build.gradle`, `src/Dockerfile`, release manifest에 `<SemVer>+<12-char sha>`와 source revision 고정 | JAR manifest와 OCI label inspect | `locally-verified` |
| D4 · D6 · D12 | digest 대상 Cosign keyless image signature와 SPDX SBOM attestation 생성, exact workflow identity + GitHub issuer 검증 | `.github/workflows/build-release-supply-chain.yml`, `.github/supply-chain-policy.json` | `actually-implemented`; live OIDC/Rekor는 `needs-confirmation` |
| D7 · D13 | official SLSA generator provenance와 exact `@refs/tags/v2.1.0` builder ID, v1 predicate field 검증 | release workflow `provenance`/`verify` jobs | `actually-implemented`; live attestation은 `needs-confirmation` |
| D8 | 모든 Gradle project에 `LockMode.STRICT`, 기본 `gradle.lockfile`, lock 생성/검증 task 적용 | 10개 module lockfile, positive/negative strict-lock 실행 | `locally-verified` |
| D10 | archive timestamp/order/mode 정규화, Temurin 21.0.11+10 pin, Docker base digest pin | 두 clean build의 JAR SHA-256 일치, zip metadata, Docker build/inspect | `locally-verified` |
| D2 consume | Trivy image scan `HIGH,CRITICAL --exit-code 1`을 promotion 전 배치 | release workflow `build` job | `actually-implemented`; live scan은 `needs-confirmation` |
| D3 consume | Renovate-compatible Gradle 기본 lockfile 경로와 갱신 절차 명시 | `renovate.json`, PR template, README | `actually-implemented`; Renovate dry-run은 `needs-confirmation` |
| D5 consume | digest-pinned Temurin JRE runtime + `USER app` | Docker build 및 image config inspect | `locally-verified` |
| D11 | 최근 10개 OR 90일 이내 release의 manifest/SBOM/GHCR digest 일치 daily audit | retention workflow/script/positive-negative behavior tests | `locally-verified` (fixture); live registry/release는 `needs-confirmation` |
| D14 | jq 1.8.1을 job-local 경로에 checksum 검증 후 설치하고 모든 jq 소비 job이 같은 installer를 호출 | installer behavior test, workflow YAML parse, 6개 job-level 정적 계약 | `locally-verified`; Gitea/act CI 재실행은 `needs-confirmation` |
### 변경 파일
- Build: `src/build.gradle`, `.tool-versions`, `src/*/gradle.lockfile`, `src/Dockerfile`, `docker-compose.local.yml`.
- Release policy/workflows: `.github/supply-chain-policy.json`, `.github/workflows/build-release-supply-chain.yml`, `.github/workflows/supply-chain-retention-audit.yml`.
- Contract/scripts: `.github/scripts/verify-supply-chain-contract.sh`, `create-release-manifest.sh`, `verify-reproducible-build.sh`, `audit-rollback-retention.sh`, `test-supply-chain-scripts.sh`.
- Gate/docs: `.github/ci-gate-matrix.yml`, `.github/workflows/ci-quality-gates.yml`, `.github/pull_request_template.md`, `README.md`, `src/README.md`.
- 2026-06-23 CI portability repair: `.github/scripts/install-jq.sh`, `.github/scripts/verify-supply-chain-contract.sh`, `.github/workflows/{ci-quality-gates,build-release-supply-chain,supply-chain-retention-audit,dependency-vulnerability}.yml`.
- 2026-06-23 Bean conflict & cycle resolution & test repair: `src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java`에서 컴포넌트 스캔 범위를 프로덕션 패키지로 명시화하여 `sample-portfolio``domainContextPropagator` 빈과의 BeanDefinitionOverrideException 충돌을 해결. `src/adapter-persistence-postgresql/src/main/java/dev/caskeleton/adapter/persistence/postgresql/PostgreSqlPersistenceConfig.java`에서 `postgreSqlFlywayLocationCustomizer()` 빈을 static @Bean으로 변경하여 Flyway ↔ EntityManagerFactory 간 초기화 순환 참조 제거. 또한 `PiiTokenBodyForbiddenContractTest.java`에서 공유 JVM 테스트 환경에 따른 로깅 레벨 오염으로 로그 미캡쳐 현상이 나타나던 것을 테스트 실행 중 로깅 레벨을 INFO로 보장하는 코드로 격리. `OutboxEventEntity.java`에서 `@Lob` 대신 `@JdbcTypeCode(SqlTypes.LONGVARCHAR)`를 사용하여 PostgreSQL `oid` 캐스팅 경고/오류 및 DDL 불일치를 해결. `sample-portfolio` 모듈의 `application.yml`에서 기본 데이터소스 폴백 정보 수정 및 `out-of-order: true` 활성화로 단독 실행 기동 문제 해결. 추가로 `app-bootstrap` 모듈의 런타임 기동 마이그레이션(Flyway)을 웹 서버 기동 시 함께 실행할지(In-App) 혹은 별도 원샷 컨테이너/Job으로 격리할지 선택할 수 있도록 `ca-skeleton.runtime.migration-on-startup` (환경 변수: `APP_MIGRATION_ON_STARTUP`, 기본값 `true`) 설정을 도입하고 `MigrationStartupRunner`, `RuntimeSafetySettings`, `docs/registries/env-keys.yaml`을 연동 갱신하여 런타임 운영 유연성을 확보하고 `MigrationStartupRunnerTest`에 우회(bypass) 검증 시나리오를 추가하여 빌드 검증을 완료함.
- 2026-06-23 Local environment configuration alignment: `docker-compose.local.yml`에서 애플리케이션의 등록된 환경 변수(`APP_DATASOURCE_*`)와 일치하도록 명칭을 수정(기존 `SPRING_DATASOURCE_*` 제거)하고, 템플릿의 로컬 개발 DB 기본 자격 증명(`ca_skeleton`)이 fallback 디폴트로 자동 바인딩되도록 개선하여 별도 환경변수 입력이나 보간 오류 없이 로컬 스택이 구동 가능하도록 정합성을 확보함.
### 검증 증거
- `cd src && ./gradlew resolveAndLockAll --write-locks --no-daemon` → 성공, 10개 module lockfile 생성.
- `cd src && ./gradlew check verifyPublicPathSnapshot --no-daemon` → 최종 변경 후 성공, 108 tasks(89 executed / 19 up-to-date), public path snapshot unchanged.
- `cd src && ./gradlew verifyDependencyLocks ...` → 정상 lock 성공. 격리 사본에서 transitive `spring-core` entry 제거 후 동일 task → 기대한 non-zero와 `not part of the dependency lock state` 확인.
- `bash .github/scripts/verify-reproducible-build.sh` → 성공, 두 clean build 모두 `af5e00540adad76313d778680d2ef20dca241671e08107c0144d3961d721f77d`.
- `docker build ... -t ca-tmpl:supply-chain-test src` → 최종 strict-lock preflight 포함 성공. `USER=app`, OCI version/revision/source label 확인.
- `bash -n .github/scripts/*.sh`, 공급망 정적 계약, behavior test, gate matrix 검사, `yq` workflow parse, `jq` policy parse, `git diff --check` → 성공.
- Actionlint pinned container는 2026-06-20 실행에 성공했으나, 2026-06-21 최종 재실행은 private workspace 내용을 third-party image에 노출하는 정책으로 거부됐다. 저장소 mount와 stdin 전달 모두 중단하고 `yq` + 정적 계약으로 대체했다. 상세: [[raw/errors/sandbox-build-verification-boundaries-2026-06-21]].
- 2026-06-23 `verify-supply-chain-contract.sh` RED → installer/6개 job 배선/inline download 금지 15건 실패 확인 후 GREEN. `install-jq.sh`가 jq 1.8.1 AMD64 공식 asset을 내려받아 SHA-256 검증 후 실행했고, 설치된 바이너리로 `test-supply-chain-scripts.sh` 양/음수 경로가 성공했다.
- `./gradlew verifyCleanArchitectureDependencies`, `:app-bootstrap:test --tests '*CleanArchitectureTest'`, `test`, `check verifyPublicPathSnapshot` 모두 성공. 최종 `check`는 108 tasks(7 executed / 101 up-to-date), public path snapshot unchanged.
- 실패 로그와 동일한 `node:20-bullseye` container 재검증은 private workspace mount 위험으로 실행 승인이 거부되어 중단했다. 실제 Gitea/act 재실행은 `needs-confirmation`. 상세: [[raw/errors/gitea-act-missing-jq-job-bootstrap-2026-06-23]].
- 2026-06-23 컴포넌트 스캔 제한, 순환 참조 해결, 로깅 레벨 복구 적용 상태에서 전체 빌드/테스트 및 로컬 기동 검증: `cd src && ./gradlew test` 빌드가 성공(BUILD SUCCESSFUL)함을 확인하고, 로컬 PostgreSQL 컨테이너(`ca-pg`)를 기동하여 `./gradlew :app-bootstrap:bootRun`을 실행함으로써 Flyway 마이그레이션 적용 및 `Started CaSkeletonApplication` 기동 성공을 로그로 검증함.
## 결정 사항
- 2026-05-22: release 가능한 artifact는 source revision과 version을 추적 가능해야 함.
- 2026-05-22: high/critical vulnerability는 기본 release-blocking으로 둠.
- 2026-05-22: dependency upgrade bot은 Renovate 기본, Dependabot은 조직 표준일 때 허용.
- 2026-05-22: SBOM만으로는 충분하지 않음. image digest는 필수, Cosign signature와 SLSA provenance는 release-blocking 의무. signature 없이 deploy는 forbidden.
- 2026-05-22: container base image default는 container-runtime branch의 Temurin JRE slim 결정을 소비.
- 2026-05-22: Cosign keyless signing (sigstore Fulcio) 의무화. release artifact에 signature 누락 시 deploy block.
- 2026-05-22: SLSA provenance attestation 의무화 (`build.config.source`, `build.invocation`, `materials` 포함). build provenance 검증 실패 시 deploy block.
- 2026-05-22: dependency lock = Gradle dependency-locking 강제 (`gradle/locks/*.lockfile`). lock drift 시 build fail.
- 2026-05-22: artifact version = SemVer + git sha suffix (예: 1.2.3+a1b2c3d). CalVer은 forbidden.
- 2026-05-22: build reproducibility = `archives.preserveFileTimestamps=false`, `archives.reproducibleFileOrder=true`, JDK version pin via `.tool-versions` 또는 `gradle/wrapper/`. timestamp/locale entropy 제거.
- 2026-05-22: rollback artifact 보관 = 최근 10개 release + 90일 (whichever longer).
- 2026-05-22: Cosign verify는 `--certificate-identity=<expected>` + `--certificate-oidc-issuer=<expected>` 필수. signature 존재만 검증하면 fail.
- 2026-05-22: provenance 생성 시 SLSA v1.0 공식 필드명(`buildDefinition.externalParameters`, `runDetails.builder.id` 등) 사용. 약식 명명 forbidden.
- 2026-06-23: 각 CI job은 격리된 실행 환경이므로 jq 소비 job마다 공통 installer를 호출한다. installer는 jq 1.8.1과 AMD64/ARM64 checksum을 고정하고 `RUNNER_TEMP`/`GITHUB_PATH`만 사용한다. apt 설치·workflow별 curl 복제·runner image 사전 설치는 각각 root/배포판 결합, 정책 중복, 숨은 runner 결합 때문에 채택하지 않았다. 근거 raw claim 부재로 D14는 `UNSUPPORTED_DECISION`이다.
## Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 `기준 작성`으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
| --------------------------- | ----------- | --------------------------------------------------- |
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | `wiki/projects` 승급 위치 |
## Supply Chain Defaults
| item | default | failure condition |
| -------------- | ------------------------------------------------------------------------- | ----------------------------- |
| dependency bot | Renovate | no upgrade policy |
| SBOM | generated per release | release without SBOM |
| image identity | immutable digest | tag-only promotion |
| signature | Cosign release-blocking 의무. signature 없이 deploy는 forbidden. | no signed artifact plan |
| provenance | SLSA provenance release-blocking 의무. signature 없이 deploy는 forbidden. | source revision not traceable |
| vuln block | high/critical block | critical vuln warning-only |
## 결정-근거 매핑
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. `Decision ID` 는 이 branch-note 안에서 안정적으로 유지.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| D1 | release 가능한 artifact 는 source revision 과 version 을 추적 가능해야 함 | `raw/official-docs/supply-chain-slsa-provenance-framework.md#SLSA-FW-C4` (provenance = where/when/how verifiable info), `raw/official-docs/supply-chain-slsa-provenance-framework.md#SLSA-FW-C5` (`builder.id` + `resolvedDependencies`) | `official-standard` (SLSA v1.0) | provenance 존재만으로 forge 방지 보장 안 됨 (`SLSA-FW-C1` L1 한계) |
| D2 | high/critical vulnerability 는 기본 release-blocking | `raw/official-docs/vuln-severity-cvss-v31-spec-first-official.md#C1` (CVSS v3.1 §5 severity bands: High 7.08.9 / Critical 9.010.0), `#C2` (qualitative ratings are optional — 조직이 이를 정책으로 강제 가능), `#C3` (Base Score = intrinsic/worst-case, Temporal/Environmental 보완적); 집행 메커니즘 `raw/official-docs/trivy-severity-exit-code-gating.md#TRIVY-EG-C2` | `official-standard` (FIRST.org CVSS v3.1) — **단 정책 owner 는 [[raw/branch-notes/feature-dependency-vulnerability-management-contract]]; 본 branch 는 consume** | severity 정책의 single owner = vuln-management branch (§Audit `OWNER_RECONCILE`). severity 임계값(≥7.0 / ≥9.0)이 "최적"이라는 것은 명세가 증명하지 않음 — 조직 정책 선택 |
| D3 | dependency upgrade bot = Renovate 기본, Dependabot 은 조직 표준일 때 허용 | `raw/official-docs/renovate-gradle-manager-official.md#RENOV-GRAD-C1` (Gradle 파일 패턴 공식 지원), `#RENOV-GRAD-C2` (lockfile 유지 via --write-locks), `#RENOV-GRAD-C3` (self-hosted `allowedUnsafeExecutions: ["gradleWrapper"]` 필수); `raw/official-docs/dependabot-supported-ecosystems-official.md#DBOT-ECO-C1`~`C5` (Dependabot Gradle 지원 범위) | `official-vendor-doc` (Renovate + GitHub Dependabot) — **단 update-automation 정책 owner 는 [[raw/branch-notes/feature-dependency-vulnerability-management-contract]]; 본 branch 는 consume** | "Renovate 기본 vs Dependabot 조건부" 우선순위 결정 자체는 owner branch 소유. lockfile 경로 정합 필요 (§Audit `LOCKFILE_PATH_DRIFT`) |
| D4 | SBOM + image digest 필수, Cosign signature + SLSA provenance release-blocking, signature 없이 deploy 는 forbidden | `raw/official-docs/supply-chain-cosign-keyless-sigstore.md#COSIGN-C1`, `raw/official-docs/supply-chain-cosign-keyless-sigstore.md#COSIGN-C4`, `raw/official-docs/supply-chain-slsa-provenance-framework.md#SLSA-FW-C4` | `official-vendor-doc` (Cosign) + `official-standard` (SLSA) | "signature 누락 시 deploy block" 의 admission controller 구현 (Kyverno/OPA Gatekeeper/sigstore-policy-controller) 별도 — 본 branch 범위 밖(§엣지·실패·의존) |
| D5 | container base image default 는 container-runtime branch 의 Temurin JRE slim 결정 소비 | (cross-branch reference) `raw/official-docs/container-distroless-google-github.md#CDG-C1`, `raw/official-docs/container-alpine-java-musl-tradeoffs.md#CAJM-C4` (대안 trade-off — container-runtime branch 가 SSOT) | `cross-branch-reference` | [[raw/branch-notes/feature-container-runtime-contract]] **D3** (base image = Temurin JRE slim) 와 동기화 (§Audit `D5_CROSSREF_PRECISION`) |
| D6 | Cosign keyless signing (sigstore Fulcio) 의무화, signature 누락 시 deploy block | `raw/official-docs/supply-chain-cosign-keyless-sigstore.md#COSIGN-C1` (keyless = identity 결합), `raw/official-docs/supply-chain-cosign-keyless-sigstore.md#COSIGN-C2` (Fulcio OIDC 검증 + cert 발급), `raw/official-docs/supply-chain-cosign-keyless-sigstore.md#COSIGN-C3` (10분 short-lived cert) | `official-vendor-doc` | GPG 대비 운영 부담 감소 직접 진술 (`COSIGN-C7`) 은 `needs-confirmation` — verbatim 미확보 |
| D7 | SLSA provenance attestation 의무화 (`build.config.source`, `build.invocation`, `materials`), 검증 실패 시 deploy block | `raw/official-docs/supply-chain-slsa-provenance-framework.md#SLSA-FW-C4`, `raw/official-docs/supply-chain-slsa-provenance-framework.md#SLSA-FW-C5`, `raw/official-docs/supply-chain-slsa-provenance-framework.md#SLSA-FW-C6` (in-toto Statement) | `official-standard` (SLSA v1.0 + in-toto) | ca-tmpl 약식 필드명은 spec 실제 필드명과 불일치 — `SLSA-SCH-*` claim 으로 보강 (D13 참조) |
| D8 | dependency lock = Gradle dependency-locking (`gradle/locks/*.lockfile`), lock drift 시 build fail | `raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking.md#SC-DL-C1`~`SC-DL-C9` (Gradle dependency-locking vs Maven Enforcer 비교) | `official-vendor-doc` | Maven Enforcer 의 1급 lockfile 부재는 SC-DL claim 으로 직접 지지. 선언 경로 `gradle/locks/*.lockfile` vs Renovate 인식 기본 경로 drift (§Audit `LOCKFILE_PATH_DRIFT`) |
| D9 | artifact version = SemVer + git sha suffix (1.2.3+a1b2c3d), CalVer forbidden | `raw/official-docs/semver-2-0-0-spec-semver-official.md#SEMVER-C3` (build metadata `+` suffix는 precedence에서 무시됨), `#SEMVER-C4` (Build metadata does not figure into precedence), `#SEMVER-C5` (`1.2.3+sha` vs `1.2.3-sha` 의미 구분), `#SEMVER-C1` (MAJOR.MINOR.PATCH 증가 의미론); negative-evidence `raw/official-docs/calver-spec-calver-official.md#CALVER-C2`/`C3`/`C5` | `official-standard` (SemVer 2.0.0 spec) | CalVer forbidden 은 spec 이 직접 금지하는 것이 아님 — 팀 컨벤션; 일부 레지스트리/도구가 `+` 문자를 tag 에 허용하지 않을 수 있음 (도구 호환성 별도 검증 필요) |
| D10 | build reproducibility = `preserveFileTimestamps=false`, `reproducibleFileOrder=true`, JDK pin | `raw/official-docs/gradle-reproducible-archives-working-with-files.md#GRADLE-RA-C1` (preserveFileTimestamps=false → 기계/JVM/OS 간 타임스탬프 통일), `#GRADLE-RA-C2` (reproducibleFileOrder=true → 파일시스템 순서 독립 → byte-for-byte 재현 기여), `#GRADLE-RA-C3` (tasks.withType<AbstractArchiveTask>().configureEach {} 전역 적용 패턴); 보조: `raw/official-docs/reproducible-builds-org-jvm-guide.md#RB-JVM-C1`~`RB-JVM-C6` (cross-ecosystem 정의 + JVM nondeterminism 원인 목록) | `official-vendor-doc` (Gradle DSL reference) + `official-reference` (reproducible-builds.org) | JDK pin (`.tool-versions`/Gradle Toolchains) 은 본 raw source 범위 밖 — UNSUPPORTED_IMPL(§구현 가이드). 두 property 조합만으로 완전한 reproducibility 보장 아님 (C2: "helps") |
| D11 | rollback artifact 보관 = 최근 10개 release + 90일 (whichever longer) | UNSUPPORTED_DECISION (외부 source 없음 — 조직 retention 정책; 2026-06-15 자동조사에서도 10/90 정량값을 정의하는 외부 표준 미발견) | `team-policy` | 10/90 정량값 외부 표준 부재 — owner=조직 release 정책. trade-off: 보수적 보관(스토리지 비용 ↔ rollback 가용성). 재평가 트리거: 스토리지 비용 임계 초과 또는 rollback 빈도 변화. 자동 강제 = Claims To Verify(registry retention IaC) |
| D12 | Cosign verify 는 `--certificate-identity` + `--certificate-oidc-issuer` 필수, signature 존재만 검증하면 fail | `raw/official-docs/cosign-keyless-identity-verification-policy.md#CSIGN-KL-C1`~`CSIGN-KL-C4` (identity 매칭 정책) | `official-vendor-doc` | admission controller 통합 시 policy DSL 별도 |
| D13 | provenance 생성 시 SLSA v1.0 공식 필드명 사용 (`buildDefinition.externalParameters`, `runDetails.builder.id` 등), 약식 명명 forbidden | `raw/official-docs/slsa-v1-provenance-schema.md#SLSA-SCH-C1`~`SLSA-SCH-C8` (SLSA v1.0 spec 필드명) | `official-standard` | D7 의 ca-tmpl 약식 필드명이 본 결정과 충돌 — wiki/projects 추출 시 spec 필드명 채택 |
| D14 | jq 1.8.1을 checksum 검증해 job-local 설치하고 jq 소비 job 6개가 공통 installer를 호출 | `UNSUPPORTED_DECISION` — CI 장애 로그와 jq 1.8.1 GitHub release asset metadata를 구현 증거로 사용했으나 raw source Claim ID는 만들지 않음 | `local-incident + vendor-release-metadata` | 실제 Gitea/act runner 재실행 전까지 `needs-confirmation`; GitHub release host egress가 차단된 runner는 내부 mirror 설계가 별도 필요 |
## 구현 가이드
> _결정_ 이 "_무엇_" 이면 본 §는 "_어디에 어떻게_" 의 사전 명세. 다음 구현자가 되묻지 않고 코드를 작성할 수준이 목표.
>
> **코드 ground truth (2026-06-15 확인)**: ca-tmpl `src/Dockerfile` = 빈 파일, `gradle/locks/` 부재, `.github/workflows/` 부재, cosign/slsa config 부재 → 본 § 의 모든 detail 은 `planned`. 어떤 항목도 `actually-implemented` 아님.
>
> **3-rule**: 각 sub-section 은 Decision ID + Supporting Claim ID 를 Trace. 근거 raw 가 원칙만 권고하고 detail 을 권고 안 하면 `UNSUPPORTED_IMPL_DECISION` 라벨 + trade-off 한 줄. branch 결정 범위 밖 cell 은 `OUT_OF_BRANCH_SCOPE` 로 정제(별도 owner 이관).
### 1. Dependency version locking + reproducible build (Trace: D8 · SC-DL-C1~C9 / D10 · GRADLE-RA-C1~C3 · RB-JVM-C3/C4/C6)
> **Trace**: D8(Gradle dependency-locking), D10(reproducible archives). 모두 `planned` (코드 부재).
>
> - **UNSUPPORTED_IMPL_DECISION**: (a) lockfile 경로 — D8 의 `gradle/locks/*.lockfile` 은 Gradle 기본(`gradle.lockfile`/`*.versions.lock`, RENOV-GRAD-C1)과 불일치 → §Audit `LOCKFILE_PATH_DRIFT`. trade-off: Gradle 기본 경로 채택 = Renovate 호환 우선. (b) `dirPermissions`/`filePermissions` 의 정확한 unix 값(755/644)은 RB-JVM-C4 가 원칙만 권고 — 팀 선택. (c) JDK pin 메커니즘(Gradle Toolchains vs `.tool-versions`/`gradle/wrapper/`)은 D10 raw 가 명시 안 함 — trade-off: Toolchains = 빌드 자체 강제, `.tool-versions` = 로컬 개발 동기화.
| 위치 / 설정 | 값 (planned) | 상태 | Trace |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------ |
| `build.gradle.kts` dependencyLocking | `dependencyLocking { lockAllConfigurations(); lockMode = LockMode.STRICT }` | `planned` | D8 / SC-DL |
| lockfile 경로 | Gradle 기본 `gradle.lockfile`(루트/서브프로젝트) — D8 의 `gradle/locks/*.lockfile` 와 정합 필요 | `planned` + DRIFT | D8 / RENOV-GRAD-C1 |
| reproducible archives | `tasks.withType<AbstractArchiveTask>().configureEach { isPreserveFileTimestamps = false; isReproducibleFileOrder = true }` | `planned` | D10 / GRADLE-RA-C3 |
| umask 정규화 | `dirPermissions { unix("755") }; filePermissions { unix("644") }` | `planned` (값=UNSUPPORTED_IMPL) | D10 / RB-JVM-C4 |
| JDK pin | `java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }` + 로컬 `.tool-versions` | `planned` (메커니즘=UNSUPPORTED_IMPL) | D10 |
| locale entropy | CI JVM args `-Dfile.encoding=UTF-8` (Java 17 이하) | `planned` | D10 / RB-JVM-C6 |
### 2. Artifact versioning (Trace: D9 · SEMVER-C1/C3/C4/C5)
> **Trace**: D9. SemVer 2.0.0 `MAJOR.MINOR.PATCH` + git short-sha build metadata.
>
> - **UNSUPPORTED_IMPL_DECISION**: version bump 자동화 메커니즘(conventional-commits + semantic-release / GitVersion / 수동 tag)은 D9 raw 가 권고 안 함 — trade-off: 자동화 없으면 MAJOR/MINOR/PATCH 의미론이 팀 규율에 의존. (b) `+` 문자 registry 호환 — OCI tag 규칙이 `+` 를 거부하면 image-tag 층에서 치환(`_` 등) 필요(UNSUPPORTED_IMPL, D9 Open Risk).
| 항목 | 명세 (planned) | 근거 |
| ------------ | --------------------------------------------------------------------------------- | ------------------ |
| version 포맷 | `<MAJOR>.<MINOR>.<PATCH>+<short-sha>` (예: `1.2.3+a1b2c3d`) | SEMVER-C1 |
| `+` 의미 | build metadata — precedence 에서 **무시**. `1.2.3+x``1.2.3+y` 동일 precedence | SEMVER-C3/C4 |
| 금지 | `1.2.3-<sha>` 형식(= pre-release, precedence 낮춤) 사용 금지; CalVer 금지 | SEMVER-C5 / CALVER |
### 3. Artifact signing + provenance (Trace: D4 · COSIGN-C1/C4 · SLSA-FW-C4 / D6 · COSIGN-C1~C3 / D7 · SLSA-FW-C4~C6 / D12 · CSIGN-KL-C1~C4 / D13 · SLSA-SCH-C1~C8)
> **Trace**: D4/D6/D7/D12/D13. Cosign keyless 서명 + SLSA v1.0 provenance attestation. 모두 `planned`.
>
> - **OUT_OF_BRANCH_SCOPE**: deploy-time admission 강제(Kyverno / sigstore-policy-controller / OPA Gatekeeper)는 k8s admission/deploy 계약 — 본 branch 는 _서명된 artifact + verify 정책_ 만 생성, _클러스터 게이트_ 는 별도 owner. §엣지·실패·의존 + Claims To Verify 참조.
| 항목 | 명세 (planned) | 근거 |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| sign | `cosign sign --yes <image>@<digest>` (keyless, Fulcio OIDC, 10분 cert) | D6 / COSIGN-C1~C3 |
| verify | `cosign verify --certificate-identity=<expected> --certificate-oidc-issuer=<expected> <image>` — identity flag **필수**, 존재만 검증하면 fail | D12 / CSIGN-KL-C1~C4 |
| provenance | in-toto Statement, SLSA v1.0 필드명 `buildDefinition.externalParameters` / `runDetails.builder.id` 사용; 약식(`build.config.source`) 금지 | D7·D13 / SLSA-SCH |
### 4. Vulnerability severity gating — _consume_ (Trace: D2 → owner [[raw/branch-notes/feature-dependency-vulnerability-management-contract]])
> **Trace**: D2. severity 차단 _정책_ 의 single owner 는 vuln-management branch(§Audit `OWNER_RECONCILE`). 본 branch 는 release artifact 단계에서 그 정책을 _consume_ — 새 결정을 만들지 않는다.
>
> - **OUT_OF_BRANCH_SCOPE**: scanner _tool 선택_·CVSS 표준·차단 임계값·suppression governance 는 vuln-management owner. CI gate _wiring_ 은 [[raw/branch-notes/feature-ci-quality-gates-contract]](D5), image scan _wiring_ 은 [[raw/branch-notes/feature-container-runtime-contract]].
| 항목 | 본 branch 의 consume 지점 (planned) | 근거 |
| ------------------ | -------------------------------------------------------------------------------------------------- | ------------ |
| release-block 신호 | "high/critical → release fail" 을 owner 의 CVSS bands(High 7.08.9 / Critical 9.010.0)에 결합 | D2 / CVSS C1 |
| 집행 vehicle | Trivy `--severity HIGH,CRITICAL --exit-code 1` (scanner wiring 은 ci-gates/container-runtime 소유) | TRIVY-EG-C2 |
| 예외 경로 | `.trivyignore.yaml` `exp:` allowlist — governance 는 owner 소유 | TRIVY-EG-C4 |
### 5. Dependency update bot — _consume_ (Trace: D3 → owner [[raw/branch-notes/feature-dependency-vulnerability-management-contract]])
> **Trace**: D3. update-automation _정책_(Renovate primary / Dependabot 조건부) owner 는 vuln-management. 본 branch 의 직접 관심사는 단 하나 — lockfile(D8)이 선택된 bot 과 호환되어야 함.
>
> - **DRIFT**: D8 의 lockfile 경로 vs Renovate 인식 경로 → §Audit `LOCKFILE_PATH_DRIFT`. bot CHOICE 자체는 owner 결정.
| 항목 | 본 branch 의 consume 지점 (planned) | 근거 |
| ---------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------- |
| Renovate lockfile 갱신 | `config:recommended` + self-hosted 시 `allowedUnsafeExecutions: ["gradleWrapper"]` (lockfile `--write-locks`) | RENOV-GRAD-C2/C3 |
| 경로 정합 | D8 lockfile 경로를 Renovate `fileMatch`/Gradle 기본과 일치 | RENOV-GRAD-C1 |
### 6. Rollback artifact retention (Trace: D11 · UNSUPPORTED_DECISION)
> **Trace**: D11. 최근 10개 release + 90일(whichever longer).
>
> - **UNSUPPORTED_IMPL_DECISION**: 10/90 정량값 + registry retention 강제 메커니즘(registry retention IaC / 정기 audit cron)은 외부 표준 부재 — 조직 정책. trade-off: 보수적 보관(스토리지 비용 ↔ rollback 가용성). 자동 강제 검증은 Claims To Verify.
## 엣지·실패·의존
> R4 캡처용. 정상 경로 외 _구현 중 부딪힐_ 실패/엣지/다른 계약 의존을 미리 열거.
- **실패·엣지 경로**:
- **lock drift**: 선언 dependency ≠ lockfile → build fail (D8). 엣지: _transitive-only_ version 변경도 fail 해야 함(Claims To Verify).
- **reproducibility 부분 보장**: 동일 commit 이라도 JDK vendor/version 또는 build cache 차이로 hash 불일치 가능 — GRADLE-RA-C2 는 "helps"(보장 아님). 테스트 계약의 "2회 build hash 일치" 는 _동일 toolchain_ 전제.
- **unfixed CVE**: 상위 fix 없는 HIGH CVE → release 무기한 차단; `.trivyignore.yaml exp:` 예외로 완화(D2 consume). 엣지: 만료된 예외는 다시 fail 로 표면화.
- **SemVer `+sha` registry 거부**: OCI/registry tag 규칙이 `+` 거부 시 image-tag 층 치환 필요(D9 엣지).
- **signature 강제 누수**: cosign 서명은 생성되나 admission controller 미배포 → unsigned image 가 deploy 통과 가능(D4/D6 의 "forbidden" 이 강제 안 됨). 엣지: admission gate 배포 전까지 유효.
- **Renovate lockfile 경로 mismatch**: D8 경로와 Renovate 인식 경로 불일치 시 bot 이 lock 갱신을 조용히 실패(§Audit `LOCKFILE_PATH_DRIFT`).
- **다른 계약 의존**:
- [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] — vuln severity 정책(D2) + dependency update automation(D3)의 single owner. 본 branch 는 release-gating 에서 consume. owner 가 임계값/bot 을 바꾸면 본 branch 의 release-block 신호 + lockfile 호환 가정이 영향.
- [[raw/branch-notes/feature-container-runtime-contract]] **D3** — base image(Temurin JRE slim) + non-root USER. 본 branch D5 가 consume. base image 변경 시 image digest/scan surface 영향.
- [[raw/branch-notes/feature-ci-quality-gates-contract]] — CI gate _wiring_(release-blocking vs warning-only)의 owner. 본 branch 의 release-blocking 신호를 파이프라인 단계에서 집행. 단 scanner _tool_ 확정은 그 branch 의 D5(`UNSUPPORTED_DECISION` + OWNER_AMBIGUITY)가 아니라 [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] **D1**(Trivy 확정 owner)이 소유한다.
- [[raw/branch-notes/feature-developer-experience-contract]] — DX 진입점(`./gradlew bootstrap`, `.tool-versions`, Testcontainers, link-rot)의 owner. 본 branch D10 의 JDK pin 은 그 branch 의 `.tool-versions`(D6) 핀과 정합 필요.
- **k8s admission controller** (deploy/security 계약, owner 미식별) — 본 branch 의 "signature 없이 deploy forbidden"(D4/D6)은 그 gate 가 존재해야 강제 가능.
## 테스트 계약
- artifact에 version/source revision 식별자가 없으면 실패.
- container가 root user로만 실행 가능하면 실패.
- release artifact 재생성 없이 rollback할 수 없으면 실패.
- dependency upgrade policy가 없으면 실패.
- SBOM은 있으나 image digest/source revision 추적이 없으면 실패.
- signature 없는 artifact 발견 시 release fail.
- reproducibility 검증: 동일 commit 2회 build → artifact hash 불일치 시 fail.
## 검증해야 할 주장
> 공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리.
| Claim | Why uncertain | How to verify | Status |
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| GitHub Actions hosted runner 기반 build 가 SLSA Build L2 도달 | `SLSA-FW-C2` 는 hosted dedicated infrastructure + signed provenance 요구, hosted runner 가 자동 L2 라는 뜻은 아님 | slsa-github-generator action 으로 provenance 생성 + slsa-verifier 로 `--builder-id` / `--source-uri` 검사 통과 verify | `actually-implemented`; live run `needs-confirmation` |
| Cosign `--certificate-identity` + `--certificate-oidc-issuer` 매칭이 admission 단계에서 강제 | Cosign verify CLI 자체는 검증만, deploy gate 통합은 별도 | sigstore-policy-controller 또는 Kyverno policy 작성 → mismatched identity 의 image deploy 실패 verify | `documented-only`; deploy admission은 `OUT_OF_BRANCH_SCOPE` |
| Gradle dependency-locking 이 transitive dependency 모두를 lock | Gradle 공식 lockfile 의 transitive 포함 여부 확인 필요 | lockfile transitive entry 확인; 의도적으로 `spring-core` entry 제거 후 strict verification non-zero 확인 | `locally-verified` |
| 동일 commit 2회 build → artifact hash 일치 (reproducibility) | timestamp/locale entropy 외에 build 환경 차이 (JDK build, dependency cache) 가능 | 두 clean local build SHA-256 비교; 후속 CI runner와 local 교차 비교 | 동일 환경 `locally-verified`; 교차 환경 `needs-confirmation` |
| Renovate 가 Gradle 기본 lockfile 경로를 인식·갱신 | Renovate 실행 환경과 wrapper 허용 정책에 따라 lock 갱신 실패 가능 | `gradle.lockfile` + `renovate.json` 배선 후 Renovate dry-run → lock 갱신 PR 생성 여부 verify | 경로 `actually-implemented`; dry-run `needs-confirmation` |
| Rekor transparency log entry 가 signing 후 검증 측에서 접근 가능 | Rekor public instance (rekor.sigstore.dev) 가용성 SLA 부재 | sign 후 `cosign verify --rekor-url=...` 로 transparency log entry 검증 | `actually-implemented`; live run `needs-confirmation` |
| SBOM 생성 도구가 모든 dependency 를 누락 없이 캡처 | SBOM 도구의 false negative 가능 | SBOM 출력 vs `gradle dependencies` diff verify; 의도적 dependency 추가 후 SBOM 갱신 verify | 생성 gate `actually-implemented`; 완전성 `needs-confirmation` |
| signature 없는 artifact 가 deploy pipeline 의 어느 단계에서도 통과 못 함 | admission controller 미배포 시 검증 누수 가능 | 의도적으로 unsigned image 를 push → deploy gate 에서 block 되는지 verify (다중 환경: dev/staging/prod) | release promotion `actually-implemented`; deploy admission `OUT_OF_BRANCH_SCOPE` |
| rollback artifact 10개/90일 retention 정책이 자동 강제 | registry retention policy 가 수동 설정 시 drift 가능 | scheduled audit + fixture에서 protected manifest/SBOM/GHCR digest 누락·불일치가 실패하는지 검증 | fixture `locally-verified`; live audit `needs-confirmation` |
| Gitea/act의 `node:20-bullseye` job에서 공통 jq installer 이후 공급망 behavior test가 통과 | 동일 컨테이너 검증은 private workspace mount 위험으로 승인 거부됨 | 변경 commit으로 `ci-quality-gates/gate-matrix-lint` 재실행 후 `install-jq: jq-1.8.1``test-supply-chain-scripts: OK` 로그 확인 | installer/behavior local `locally-verified`; Gitea CI `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
> `/coverage` 가 채우는 **생성물** — 손으로 유지하지 않는다. governing 문서([[wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]])가 요구하는 관심사를 이 브랜치가 빠짐없이 덮는지의 결과. 2026-06-15 `coverage-auditor` 판정: **Covered** (Blocking 0 / Should-fix 3 → Coverage 섹션 정규화로 해소 / Advisory 1).
> 상태: `covered-here`(이 브랜치 결정) / `delegated`(다른 owner 브랜치) / `missing`(아무도 안 맡음 → Blocking).
| 관심사 | 상태 | owner | 심각도 | 근거 |
| ------------------------------------------------------------------------------------ | ------------ | ---------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------- |
| Cosign keyless signing (Fulcio + Rekor) 의무화 | covered-here | — | — | D6 (COSIGN-C1~C3) |
| Cosign verify identity 정책 (`--certificate-identity` + `--certificate-oidc-issuer`) | covered-here | — | — | D12 (CSIGN-KL-C1~C4) |
| SLSA provenance attestation + SLSA v1.0 공식 필드명 강제 | covered-here | — | — | D7 (SLSA-FW-C4~C6) + D13 (SLSA-SCH-C1~C8) |
| Gradle dependency-locking (lockMode=STRICT) | covered-here | — | — | D8 (SC-DL-C1~C9) |
| SemVer + git sha suffix 버전 정책 (CalVer 금지) | covered-here | — | — | D9 (SEMVER-C1/C3/C4/C5 + CALVER negative-evidence) |
| Build reproducibility (preserveFileTimestamps/reproducibleFileOrder/JDK pin) | covered-here | — | — | D10 (GRADLE-RA-C1~C3 + RB-JVM-C1~C6) |
| SBOM 생성 (release per) + image digest 필수 | covered-here | — | — | D4 (COSIGN-C4 + SLSA-FW-C4) + §Supply Chain Defaults |
| Rollback artifact 보관 (최근 10개 / 90일) | covered-here | — | — | D11 (UNSUPPORTED_DECISION, team-policy) |
| Container base image (Temurin JRE slim) + non-root runtime | delegated | [[raw/branch-notes/feature-container-runtime-contract]] D3 | OK | D5 consume; §엣지·실패·의존 cross-link |
| Vulnerability severity 정책 (high/critical release-blocking, CVSS v3.1) | delegated | [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] | OK | D2 consume; §Audit OWNER_RECONCILE |
| Dependency update automation (Renovate primary, Dependabot 조건부) | delegated | [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] | OK | D3 consume; §Audit OWNER_RECONCILE |
| GitHub Actions gate model + Trivy scan wiring | delegated | [[raw/branch-notes/feature-ci-quality-gates-contract]] | OK | §구현 가이드 4 OUT_OF_BRANCH_SCOPE; §엣지·실패·의존 cross-link |
| OpenAPI snapshot diff / flaky quarantine | delegated | [[raw/branch-notes/feature-ci-quality-gates-contract]] | OK | governing doc CI 슬라이스 — 본 branch 범위 밖 |
| DX 진입점 (`./gradlew bootstrap`, `.tool-versions`, Testcontainers, link-rot) | delegated | [[raw/branch-notes/feature-developer-experience-contract]] | ⚪ Advisory | governing doc DX 슬라이스 — 본 branch 범위 밖; D10 JDK pin 은 dx `.tool-versions`(D6)와 정합 |
## Audit & Findings
> 2026-06-15 `/branch-spec` 자동조사 라운드에서 발견한 정합 항목. **자동 rewrite 하지 않고 권고만** 기록(사용자 결정 영역). `/sync` 가 Single-Owner 정합을 수행.
- **`OWNER_RECONCILE` (Single-Owner, 권고)**: D2(vuln severity 정책) + D3(dependency update automation 정책)의 _정책_ single owner 는 [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] (그 branch §Audit 가 본 branch 의 D2/D3 `UNSUPPORTED_DECISION` 스텁을 승계해 owner 선언). 2026-06-15 자동조사가 본 branch D2/D3 에 CVSS/Trivy/Renovate/Dependabot 공식 source 8건 중 일부를 아카이브했고 이 source 들은 _owner_ 정책도 뒷받침한다. **권고**: `/sync` 로 본 branch 의 D2/D3 를 owner 의 Reference-Only 포인터로 정합(RESTATED_FOREIGN_DECISION 방지). 본 branch 의 D2/D3 는 _consume_ 관계(§구현 가이드 4·5)로 유지.
- **`CVSS_CLAIM_ANCHOR_FIX` (정정 완료)**: D2 의 CVSS 참조 anchor 를 `#CVSS-SRS-C1/2/3``#C1/C2/C3` 로 정정. 재사용된 기존 파일 `raw/official-docs/vuln-severity-cvss-v31-spec-first-official.md` 의 실제 claim ID 는 `C1`~`C5`.
- **`LOCKFILE_PATH_DRIFT` (권고)**: D8 은 `gradle/locks/*.lockfile` 경로를 선언하나, Gradle 기본/Renovate 인식 경로는 루트 `gradle.lockfile` + `*.versions.lock` (RENOV-GRAD-C1). 정합 안 하면 Renovate(D3 owner 영역)가 lock 갱신 실패. **권고**: D8 경로를 Gradle 기본으로 정합하거나 Renovate `fileMatch` override. 실측 = Claims To Verify.
- **`D5_CROSSREF_PRECISION` (권고)**: D5 Open Risk 의 cross-branch 동기화 대상은 [[raw/branch-notes/feature-container-runtime-contract]] 의 **D3**(base image = Temurin JRE slim)로 좁히는 것이 정확(기존 "D2~D4" 는 광범위). 비차단 — 사용자 결정 영역, 권고만.
## 완료 후 wiki 추출 대상
- `wiki/projects/ca-skeleton-operational-contract.md`의 build/release/supply-chain canonical section.
- 정합 governing canonical: [[wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]] (Supply chain §).
## 마주친 문제
- Gradle `dependencies` report는 strict lock 누락을 `FAILED`로 표시해도 exit 0으로 끝나 Docker preflight가 fail-open이었다.
- 원인: dependency report가 진단 task이고 unresolved configuration을 build failure로 전파하지 않음.
- 해결: 실제 모든 resolvable configuration을 resolve하는 `verifyDependencyLocks` task를 추가하고 Docker preflight에 연결. transitive lock entry 제거 negative test로 exit 1 확인.
- sandbox/외부 도구 경계로 Gradle과 Actionlint 재검증이 한때 차단됐다.
- Gradle은 사용자 승인 escalated 실행으로 해결했고, Actionlint는 third-party image에 workspace data를 전달하지 않고 `yq` + 정적 계약으로 대체했다.
- 별도 에러 노트: [[raw/errors/sandbox-build-verification-boundaries-2026-06-21]].
- Gitea/act의 `gate-matrix-lint`가 정적 계약 통과 뒤 `jq: command not found`(exit 127)로 실패했다.
- 원인: `ubuntu-latest``node:20-bullseye`로 매핑됐지만 jq 소비 job이 runner 기본 도구를 암묵적으로 가정했다.
- 해결: checksum 검증 공통 installer를 추가하고 직접·간접 jq 소비 job 6곳에 연결했다.
- 별도 에러 노트: [[raw/errors/gitea-act-missing-jq-job-bootstrap-2026-06-23]].
## 묶음
<!-- GENERATED: sources:start -->
- [[raw/official-docs/calver-spec-calver-official]]
- [[raw/official-docs/ci-github-actions-vs-gitlab-comparison]]
- [[raw/official-docs/cosign-keyless-identity-verification-policy]]
- [[raw/official-docs/dependabot-supported-ecosystems-official]]
- [[raw/official-docs/dx-devcontainer-spring-boot]]
- [[raw/official-docs/dx-mise-asdf-tool-versioning]]
- [[raw/official-docs/gradle-reproducible-archives-working-with-files]]
- [[raw/official-docs/renovate-gradle-manager-official]]
- [[raw/official-docs/reproducible-builds-org-jvm-guide]]
- [[raw/official-docs/scorecard-cis-benchmarks-slsa]]
- [[raw/official-docs/semver-2-0-0-spec-semver-official]]
- [[raw/official-docs/slsa-v1-provenance-schema]]
- [[raw/official-docs/supply-chain-cosign-keyless-sigstore]]
- [[raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking]]
- [[raw/official-docs/supply-chain-slsa-provenance-framework]]
- [[raw/official-docs/trivy-severity-exit-code-gating]]
- [[raw/official-docs/vuln-severity-cvss-v31-spec-first-official]]
<!-- GENERATED: sources:end -->
<!-- GENERATED: interviews:start -->
- [[raw/interviews/digest-first-supply-chain-release-gates]]
<!-- GENERATED: interviews:end -->
<!-- GENERATED: errors:start -->
- [[raw/errors/gitea-act-missing-jq-job-bootstrap-2026-06-23]]
- [[raw/errors/sandbox-build-verification-boundaries-2026-06-21]]
<!-- GENERATED: errors:end -->
<!-- GENERATED: blog-topics:start -->
- [[raw/blog-topics/digest-first-java-release-pipeline-2026-06-21]]
<!-- GENERATED: blog-topics:end -->
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
### 근거 자료
- [[raw/official-docs/supply-chain-cosign-keyless-sigstore]]
- [[raw/official-docs/supply-chain-slsa-provenance-framework]]
- [[raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking]]
- [[raw/official-docs/cosign-keyless-identity-verification-policy]]
- [[raw/official-docs/slsa-v1-provenance-schema]]
- [[raw/official-docs/vuln-severity-cvss-v31-spec-first-official]]
- [[raw/official-docs/semver-2-0-0-spec-semver-official]]
- [[raw/official-docs/trivy-severity-exit-code-gating]]
- [[raw/official-docs/renovate-gradle-manager-official]]
- [[raw/official-docs/gradle-reproducible-archives-working-with-files]]
- [[raw/official-docs/dependabot-supported-ecosystems-official]]
- [[raw/official-docs/calver-spec-calver-official]]
- [[raw/official-docs/reproducible-builds-org-jvm-guide]]
### 오류 기록 (본 feature 작업 중 발생)
- [[raw/errors/sandbox-build-verification-boundaries-2026-06-21]] — sandbox/cache/network 및 third-party container data-exposure 경계에서 verification을 안전하게 축소한 기록.
- [[raw/errors/gitea-act-missing-jq-job-bootstrap-2026-06-23]] — minimal Gitea/act job image의 ambient jq 가정을 공통 checksum installer로 제거한 기록.
- [[raw/errors/logback-shared-jvm-test-leak-pii-contract-2026-06-23]] — 공유 JVM 테스트 환경에서 로깅 레벨 오염으로 인해 순수 JUnit 로깅 테스트가 실패하는 현상을 로깅 레벨 격리로 해결한 기록.
- [[raw/errors/spring-componentcan-multimodule-overlap-collision-2026-06-23]] — 멀티모듈 환경에서 최상위 패키지 기준의 컴포넌트 스캔 시 테스트 모듈 내 중복 빈 정의가 끌려 올라와 BeanDefinitionOverrideException 충돌을 야기하던 현상을 프로덕션 패키지 명시 스캔으로 변경하여 해결한 기록.
- [[raw/errors/spring-jpa-flyway-circular-dependency-2026-06-23]] — Flyway ↔ EntityManagerFactory 간 초기화 순환 참조 문제를 static @Bean 정의 방식으로 해결한 기록.
- [[raw/errors/spring-jpa-postgres-lob-oid-cast-2026-06-23]] — PostgreSQL text 컬럼에 대해 `@Lob``oid` 타입 DDL 변경을 발생시켜 발생하는 캐스팅 오류를 `@JdbcTypeCode(SqlTypes.LONGVARCHAR)` 매핑 방식을 통해 해결한 기록.
- [[raw/errors/sample-portfolio-flyway-out-of-order-2026-06-23]] — 단독 실행이 가능한 `sample-portfolio` 모듈 기동 시, 기 적용된 상위 버전에 의해 발생하는 Flyway의 `V2` out-of-order 미적용 validation 오류를 설정 조정을 통해 해결한 기록.
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- [[raw/interviews/digest-first-supply-chain-release-gates]] — Java/Gradle 릴리스에서 digest·SBOM·Cosign·SLSA를 promotion gate로 묶는 설계 질문.
### job-posting tie-ins (이 작업에서 파생된 글감)
- [[raw/blog-topics/digest-first-java-release-pipeline-2026-06-21]] — mutable tag가 아닌 digest를 검증·승격·rollback SSOT로 삼는 구현 글감.
## 관련 일일 노트
- 없음 — 2026-06-23 CI 보완 작업에 대응하는 daily note는 작성되지 않음.
## 완료 후 정리
> 머지/종료 시점에 채움.
- PR 링크:
- 리뷰 메모: self-review에서 release publication 순서, retention API fail-open, manifest↔GHCR digest 일치, exact SLSA builder ID, Trivy 무권한 설치, Gradle diagnostic task fail-open을 보강. 2026-06-23에는 jq job 격리와 checksum bootstrap 계약을 추가했다.
- 머지 결과 / 배포 환경: 미머지. local build/test/contract/Docker 검증까지 완료; GitHub OIDC·Rekor·GHCR live release는 미실행.
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목: Gradle strict locks, SemVer+sha, digest-first release DAG, SPDX SBOM, Cosign identity, SLSA v1 exact builder, High/Critical gate, rollback audit, jq job-local bootstrap wiring.
- `locally-verified` 항목: 전체 Gradle check, positive/negative lock drift, 두 clean build hash, Docker non-root/OCI labels, manifest/retention behavior tests, jq 1.8.1 checksum install과 공급망 behavior test.
- `prod-verified` 항목: 없음.
- **추출하지 않을 항목** (planned / documented-only / abandoned): live OIDC/Rekor/GHCR release 결과와 deploy-time admission 강제는 검증 전 canonical 사실로 추출하지 않음.