423 lines
43 KiB
Markdown
423 lines
43 KiB
Markdown
---
|
||
title: branch / feature-ci-quality-gates-contract
|
||
source_type: branch-note
|
||
status: raw
|
||
branch: feature-ci-quality-gates-contract
|
||
parent_branch:
|
||
related_projects: [ca-skeleton]
|
||
governing_docs: [wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]
|
||
tags: [branch, ca-skeleton, ci, quality-gate, contract-test]
|
||
created: 2026-05-22
|
||
target_merge:
|
||
status_label: in-progress
|
||
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-028
|
||
kind: project-work-item
|
||
project: ca-skeleton-operational-contract
|
||
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-028
|
||
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-OPENAPI-001@1, DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-TEST-001@1]
|
||
refines: []
|
||
overrides: []
|
||
depends_on: []
|
||
contract_packet: 1
|
||
contract_packet_sha256: e13ec9fd666d546ce8cb089f48f43da5ed3d77a72d0c6edc691bad11e65956e8
|
||
---
|
||
|
||
# branch: feature-ci-quality-gates-contract
|
||
|
||
> Layer: `raw/branch-notes/` — skeleton 계약이 문서에만 남지 않도록 CI에서 강제할 quality gate 기준을 정의합니다.
|
||
|
||
<!-- 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`
|
||
- **완료 조건**: architecture·contract·OpenAPI blocking gate가 분리 실행된다
|
||
|
||
<!-- section-id: inherited-project-decisions -->
|
||
### 상속한 프로젝트 결정
|
||
|
||
| Decision Ref | Project Summary | Branch Application | Source |
|
||
|---|---|---|---|
|
||
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-OPENAPI-001@1` | verification suite가 OpenAPI drift의 release-blocking 판정권을 소유한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
||
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-TEST-001@1` | test framework는 JUnit 5다 | 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 -->
|
||
## 목표
|
||
|
||
운영 계약은 깨지기 쉽습니다. response envelope, log schema, env fail-fast, OpenAPI drift, repository capability, security/log leakage 같은 항목은 CI에서 실패 조건으로 고정해야 합니다.
|
||
|
||
본 branch 의 책임은 **gate wiring(어떤 gate 가 CI 에서 어떻게 실행/차단되는가)** 이다. 개별 scanner/tool/severity *정책 결정* 은 전용 owner branch 가 소유하고 본 branch 는 그 결과를 release-blocking gate 로 *배선* 한다 (§구현 가이드 §6, §엣지·실패·의존 의존 목록).
|
||
|
||
- 이슈:
|
||
- PR:
|
||
|
||
<!-- section-id: branch-scope -->
|
||
## 범위
|
||
|
||
### 포함 범위
|
||
|
||
- format/lint/test/contract test gate.
|
||
- OpenAPI drift check.
|
||
- dependency vulnerability scan gate.
|
||
- optional adapter test matrix.
|
||
- warning-only와 release-blocking gate 구분.
|
||
|
||
### 제외 범위
|
||
|
||
- 실제 CI provider workflow 구현 세부.
|
||
- 배포 승인 프로세스.
|
||
- load/performance test.
|
||
|
||
## 근거 (필수, 최소 1개+)
|
||
|
||
> 본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
|
||
|
||
| Source | 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/official-docs/ci-github-actions-vs-gitlab-comparison]] | GitHub Actions `needs:` + `if: success( |
|
||
| [[raw/official-docs/ci-openapi-snapshot-diff-tooling]] | springdoc + openapi-diff (Tufin/oasdiff |
|
||
| [[raw/company-tech-blogs/ci-flaky-test-quarantine-spotify-google]] | Spotify/Google/MS quarantine 인정 vs Fowler 반대 |
|
||
|
||
## 외부 근거 / 대안 조사 (2026-05-22 — Group G-E: CI Quality Gates)
|
||
|
||
본 branch의 Gate ownership matrix 20행 + flaky quarantine 14d + OpenAPI snapshot diff 결정에 대한 외부 source.
|
||
|
||
- **채택 결정 (GitHub Actions + matrix gate + flaky 14d sunset)**:
|
||
- [[raw/official-docs/ci-github-actions-vs-gitlab-comparison]] — GitHub Actions `needs:` + `if: success()`가 ca-tmpl contract gate 모델과 정확히 맞물림
|
||
- [[raw/official-docs/ci-openapi-snapshot-diff-tooling]] — springdoc + openapi-diff (Tufin/oasdiff)
|
||
- **검토한 대안**:
|
||
- **대안 1: GitLab CI vs GitHub Actions** — 동일 source에서 비교. **선택 조건**: ca-tmpl repo 가 GitHub 호스팅 → GitHub Actions 채택; GitLab 호스팅으로 이전 시 `needs` ↔ `stages` 매핑(`CIGG-C2`)으로 이식 가능 (provider 선정 자체는 별도 ADR — `CIGG-C2` 는 매핑 *가능성* 만 보장)
|
||
- **대안 2: Jenkins / Tekton (k8s-native)** — k8s 인프라/plugin 의존도로 skeleton 단계에 과함
|
||
- **대안 3: CircleCI / Buildkite / Drone CI** — vendor 다양성, ca-tmpl scope 외
|
||
- **사례 (flaky quarantine)**: [[raw/company-tech-blogs/ci-flaky-test-quarantine-spotify-google]] — Spotify/Google/MS quarantine 인정 vs Fowler 반대. ca-tmpl 14일 sunset은 절충안
|
||
- **비교 핵심**: GitHub Actions의 `needs:`/`if:` gate 의존성 모델이 ca-tmpl 11 release-blocking gate에 정확히 맞물림. Tekton/Jenkins는 k8s 인프라/plugin 부담으로 skeleton에 과함. Flaky quarantine은 Spotify/Google/MS 인정 vs Fowler 반대 양립 — ca-tmpl 14일 sunset이 절충.
|
||
|
||
## TODO
|
||
|
||
> TODO drained 2026-05-22 — required CI gate 목록 / release-blocking vs warning-only 기준 / contract test 차단 / optional adapter matrix / OpenAPI drift / vulnerability 차단 정책 모두 "결정 사항"과 "Gate ↔ Branch Contract Test 소유권 매트릭스"에 반영됨. 잔존 TODO 없음.
|
||
|
||
## 진행 중 메모
|
||
|
||
- 2026-06-15 (`/branch-spec`): pre-template 노트를 현 템플릿 구조로 보강 — 누락 섹션(구현 가이드 / 엣지·실패·의존 / 진행 중 메모 / 관련 일일 노트) 추가, `parent_branch` + `governing_docs` frontmatter 추가, §Coverage seed, §Audit & Findings(ground-truth drift) 추가. 기존 결정·매트릭스·테스트 계약 본문은 verbatim 보존. ca-tmpl ground truth 대조 결과 모든 gate 는 여전히 `documented-only`(`.github/workflows/` 부재 확인) — actually-implemented 주장 없음.
|
||
- 2026-06-20 (구현): gate wiring 을 `actually-implemented`(로컬 `locally-verified`)로 승급. 산출물 — `.github/workflows/ci-quality-gates.yml`(9 잡: quality-gates/security-snapshot-gates/openapi-drift/sample-removal-smoke/optional-adapter-matrix/gate-matrix-lint/breaking-change-approval/quarantine/**release-gate** fan-in), `.github/ci-gate-matrix.yml`(20행 in-repo SSOT), `.github/scripts/verify-gate-matrix.sh`(C7 cross-check), `flaky-quarantine.yaml`(repo-루트 레지스트리, 빈 버킷), `src/build.gradle`(`test` excludeTags 'quarantine' + `quarantineTest` 버킷 + `verifyQuarantineSunset` 14일 sunset, check 연결), `.github/CODEOWNERS`/`.github/pull_request_template.md`(D8 escape-hatch 거버넌스), `src/README.md` 문서.
|
||
- **핵심 구현 결정 (UNSUPPORTED_IMPL_DECISION 해소):**
|
||
- **§4 quarantine 메커니즘 = `@Tag("quarantine")`(JUnit 기본, 전 모듈 즉시 사용) + repo-루트 `flaky-quarantine.yaml` 레지스트리** — note 의 `@QuarantinedSince` custom annotation 후보 대신 채택. 이유: custom annotation 의 cross-module 사용은 test-fixtures/신규 모듈 plumbing 필요(과함)이고, `shared-contract`(stdlib-only)에 JUnit 타입을 둘 수 없음. 레지스트리 방식은 기존 4개 거버넌스 게이트(verifyTrivyignore/verifyEnvKeys/verifyOneTypePerFile/verifyPublicPathSnapshot)와 동형이며 gitignored-docs 제약(아래)도 회피.
|
||
- **release-gate fan-in = `if: always()` + `needs.*.result` 스캔** — `if: success()` 단독은 상위 실패 시 aggregator 가 skipped(차단 아님). Claim C1 의 실증적 해소.
|
||
- **gitignored 설정 제약 발견:** `.gitignore` 가 `/docs` 전체 제외(0 tracked) → CI-read 신규 파일은 `docs/` 금지, tracked 경로(repo 루트/`.github/`)에 배치(`.trivyignore.yaml` 선례). `check` 의 registry 의존은 워크플로 "RUNTIME-CONFIG PREREQUISITE" 로 문서화(범위 밖 — env-driven 소유).
|
||
- 검증(로컬): verifyQuarantineSunset 3종 control(empty→OK / over-age 170d→fail / drift unregistered→fail), `:shared-contract:quarantineTest` BUILD SUCCESSFUL(빈 버킷), gate-matrix-lint PASS(20=16 verified+4 delegated), openapiCheckSnapshot/SampleRemovalSmoke/TestTaxonomyArchitectureTest/verifyCleanArchitectureDependencies 통과, 워크플로 YAML PyYAML 파싱 OK. **CI 실제 실행은 `needs-confirmation`.**
|
||
- 파생 노트: [[raw/errors/ci-fan-in-skipped-not-failed-and-gitignored-config-2026-06-20]], [[raw/blog-topics/ci-gate-wiring-vs-policy-ownership-2026-06-20]], [[raw/interviews/ci-release-gate-fan-in-blocking-2026-06-20]].
|
||
|
||
## 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` 승급 위치 |
|
||
|
||
## 결정 사항
|
||
|
||
- 2026-05-22: contract violation은 warning-only로 두지 않음.
|
||
- 2026-05-22: optional adapter test는 adapter enabled matrix에서만 실행.
|
||
- 2026-05-22: OpenAPI drift, registry drift, security/privacy leakage, architecture boundary, sample removal smoke는 release-blocking.
|
||
- 2026-05-22: warning-only는 dependency freshness advisory처럼 release artifact correctness를 직접 깨지 않는 항목에만 허용.
|
||
- 2026-05-22: vulnerability scanner = Trivy (image + dependency). suppression은 `trivy-ignore` 파일 + PR review approval 필수.
|
||
- 2026-05-22: OpenAPI drift ground truth = code-generated snapshot (springdoc 등). hand-maintained는 forbidden.
|
||
- 2026-05-22: flaky test quarantine bucket 허용. quarantine된 test는 별도 gradle task로 분리, sunset deadline 14일.
|
||
- 2026-05-22: contract test snapshot 의도적 갱신 = breaking change catalog row 인용 + PR label `intent:breaking-change-approved`로 escape hatch.
|
||
- 2026-05-22: 본 branch가 **flaky test quarantine SSOT** (sunset 14일). test-taxonomy-fixture-contract는 consumer (flaky 발생 시 quarantine bucket 참조).
|
||
|
||
## 결정-근거 매핑
|
||
|
||
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. `Decision ID` 는 이 branch-note 안에서 안정적으로 유지.
|
||
|
||
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|
||
|---|---|---|---|---|
|
||
| D1 | contract violation은 warning-only로 두지 않음 (release-blocking) | UNSUPPORTED_DECISION (외부 source 직접 증명 없음 — 조직 policy 결정) | `team-policy` | release-blocking 강도 자체의 외부 표준 부재 |
|
||
| D2 | optional adapter test는 adapter enabled matrix에서만 실행 | `raw/official-docs/ci-github-actions-vs-gitlab-comparison.md#CIGG-C3` (GitHub Actions `needs` key 로 job 의존성 명시) | `official-vendor-doc` (matrix job 표현은 vendor docs 에서 직접 지원) | `strategy.matrix` 의 정확한 표현은 본 인용 범위 밖 — 별도 GitHub Actions matrix 문서 raw 등록 권고 |
|
||
| D3 | OpenAPI drift, registry drift, security/privacy leakage, architecture boundary, sample removal smoke 는 release-blocking | `raw/official-docs/ci-openapi-snapshot-diff-tooling.md#CIOS-C4`, `raw/official-docs/ci-openapi-snapshot-diff-tooling.md#CIOS-C5`, `raw/official-docs/ci-github-actions-vs-gitlab-comparison.md#CIGG-C2` | `official-vendor-doc` | breaking change 판정 규칙 차이 (openapi-diff vs oasdiff) 별도 검증 필요 |
|
||
| D4 | warning-only는 dependency freshness advisory 처럼 release artifact correctness 를 직접 깨지 않는 항목에만 허용 | UNSUPPORTED_DECISION (외부 source 직접 증명 없음 — 조직 분류 정책) | `team-policy` | freshness advisory 와 vulnerability advisory 의 경계 정의 필요 |
|
||
| D5 | vulnerability scanner = Trivy (image + dependency), suppression 은 `trivy-ignore` + PR review approval 필수 | UNSUPPORTED_DECISION (Trivy 공식 docs raw source 없음) — **+ OWNER_AMBIGUITY**: scanner *tool 선택* 은 본 branch(gate wiring) 범위 밖 후보. [[raw/branch-notes/feature-dependency-vulnerability-management-contract]](현재 scanner 미결) 또는 severity 정책 owner [[raw/branch-notes/feature-build-release-supply-chain-contract]] 로 위임 권고 (§Audit) | `team-convention` | Trivy 공식 페이지 raw source 보강 필요 + tool 선택 owner 미확정 |
|
||
| D6 | OpenAPI drift ground truth = code-generated snapshot (springdoc 등), hand-maintained 는 forbidden | `raw/official-docs/ci-openapi-snapshot-diff-tooling.md#CIOS-C1`, `raw/official-docs/ci-openapi-snapshot-diff-tooling.md#CIOS-C2`, `raw/official-docs/ci-openapi-snapshot-diff-tooling.md#CIOS-C3` | `official-vendor-doc` (springdoc runtime introspection 의 공식 동작) | springdoc 은 dynamic routing (WebFlux functional routes) 일부 누락 위험 — `CIOS-C1` 의 inferred semantics 한계 |
|
||
| D7 | flaky test quarantine bucket 허용, 별도 gradle task 로 분리, sunset deadline 14일 | UNSUPPORTED_DECISION (`raw/company-tech-blogs/ci-flaky-test-quarantine-spotify-google.md` 는 company-case-study — 공식 best practice 로 단정 불가) | `company-case-study` (Spotify/Google/MS 인정 vs Fowler 반대 양립) | 14일 sunset 정량값은 ca-tmpl 절충안 — 외부 표준 부재 |
|
||
| D8 | contract test snapshot 의도적 갱신 = breaking change catalog row 인용 + PR label `intent:breaking-change-approved` escape hatch | UNSUPPORTED_DECISION (조직 governance 결정 — 외부 source 직접 증명 없음) | `team-policy` | label 권한 정책 (`feature-contract-verification-test-suite` D-Verify Claim 과 cross-link) |
|
||
| D9 | 본 branch 가 flaky test quarantine SSOT (sunset 14일), test-taxonomy-fixture-contract 는 consumer | UNSUPPORTED_DECISION (cross-branch ownership 결정) | `team-policy` | 본 branch ↔ test-taxonomy branch 간 ownership 경계 명문화 |
|
||
|
||
> Note: `ci-flaky-test-quarantine-spotify-google` 는 `company-tech-blog` 카테고리이므로 본 branch 의 quarantine 정책은 `company-case-study` 강도만 가지며 official best practice 로 표현 금지.
|
||
|
||
## 구현 가이드
|
||
|
||
> *결정 (Decisions)* 이 "*무엇* 을 할 것인가" 라면, 본 §는 "*어디에 어떻게* 구현될 것인가" 의 사전 명세. 본 branch 의 핵심 산출물 카탈로그(gate 목록 + owner 매핑)는 `## Gate ↔ Branch Contract Test 소유권 매트릭스`(SSOT, 20행). 본 §는 그 매트릭스가 담지 못하는 **wiring 메커니즘**(needs/if 위상, OpenAPI gate step, flaky 강제, escape hatch, 위임 경계)을 결정·근거 reference 와 함께 명세한다.
|
||
|
||
### 1. Gate 위계 — release-blocking vs warning-only 분류 규칙
|
||
|
||
> **Trace**: D1 (contract violation = release-blocking) + D3 (release-blocking 목록) + D4 (warning-only 한정). Supporting: `CIGG-C2` (stages↔needs gate 의존성 모델), team-policy.
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: "release-blocking" 강도 자체(D1/D4)는 조직 policy — 외부 표준 부재. trade-off: 엄격 차단(merge 속도 ↓, 계약 안전 ↑) vs warning-only 완화(반대). freshness advisory 와 vulnerability advisory 의 경계(D4 Open Risk)도 조직 분류.
|
||
|
||
- 분류 규칙: **release artifact correctness 를 직접 깨는 gate = release-blocking**(D3 목록 + 매트릭스 release-blocking 열), **freshness advisory 류만 warning-only**(D4).
|
||
- 전체 gate 목록·owner·release-blocking 여부 = `## Gate ↔ Branch Contract Test 소유권 매트릭스`(SSOT). 본 sub-section 은 *판정 규칙*만, 카탈로그는 매트릭스가 소유(중복 금지).
|
||
|
||
### 2. GitHub Actions gate 위상 (needs
|
||
|
||
> **Trace**: D2 (optional adapter = enabled matrix only) + D3. Supporting: `CIGG-C3` (job 의존성 = `needs` key), `CIGG-C2` (GitLab `stages` ↔ GitHub `needs` 매핑).
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: ① `strategy.matrix` 의 정확한 yaml shape(adapter-enabled 조합 표현) — `CIGG-C3` 는 `needs` key 만 보장, matrix 표현은 인용 범위 밖. trade-off: 별도 GitHub Actions matrix vendor 문서 raw 등록 필요(D2 Open Risk). ② fan-in 시 status 전파(`if: success()` vs `if: always()`)의 정확한 규칙 — `CIGG-C3` 미보장(§엣지 Claim 1 로 검증 위임).
|
||
|
||
- 각 contract-test job 은 release job 의 `needs:` 의존성으로 선언, `if: success()` 로 release gate.
|
||
- optional adapter test(D2)는 `strategy.matrix` 의 adapter-enabled 조합에서만 실행 → 매트릭스 행 "integration test (optional adapter matrix) | true if matrix enabled".
|
||
|
||
### 3. OpenAPI drift gate
|
||
|
||
> **Trace**: D6 (ground truth = code-generated snapshot, hand-maintained forbidden) + D3 (release-blocking). Supporting: `CIOS-C1`/`C2`/`C3` (springdoc runtime introspection), `CIOS-C4` (openapi-diff 3.x 비교), `CIOS-C5`/`C6` (oasdiff breaking 서브명령).
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: ① gradle task 명 `openapiCheckSnapshot` — note 자체 명명, 인용 외. trade-off: 명명 임의(되묻기 방지용 고정). ② exit-code 기반 차단 — `CIOS-C5` 가 breaking 시 non-zero exit 을 직접 보장하지 않음(§엣지 Claim 2 검증 위임).
|
||
|
||
- baseline `openapi-snapshot.yaml`(checked-in) vs build 시 springdoc-generated OpenAPI 를 `oasdiff breaking`(또는 openapi-diff)으로 비교, breaking 1건+ 이면 release-block.
|
||
- 알려진 한계: springdoc 은 WebFlux functional route 등 dynamic routing 일부 누락 가능(`CIOS-C1`).
|
||
|
||
### 4. Flaky test quarantine bucket (본 branch SSOT, 14d sunset)
|
||
|
||
> **Trace**: D7 (quarantine bucket + 별도 gradle task + 14일 sunset) + D9 (본 branch = SSOT, test-taxonomy = consumer). Supporting: `company-case-study`(Spotify/Google/MS) + team-policy.
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: `@QuarantinedSince` annotation 명 + CI step 의 14일 초과 build-fail 자동 강제 메커니즘 — 외부 source 없음(company-case-study 는 quarantine *인정* 만, 14d 정량·강제 메커니즘 무). trade-off: 14d 는 ca-tmpl 절충값; 자동 강제 미구현 시 수동 리뷰로 대체(§엣지 Claim 5).
|
||
|
||
- quarantine bucket = 별도 gradle task 로 분리(메인 gate 에서 격리). [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] 는 flaky 발생 시 본 bucket 을 참조하는 consumer.
|
||
|
||
### 5. Snapshot 의도적 갱신 escape hatch
|
||
|
||
> **Trace**: D8 (breaking change catalog row 인용 + PR label `intent:breaking-change-approved`). Supporting: team-policy (governance).
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: label 부여 권한 정책(누가 label 을 달 수 있나) — 외부 source 없음. trade-off: branch protection + CODEOWNERS 로 label 권한 제한 필요(§엣지 Claim 6); 미설정 시 누구나 우회.
|
||
|
||
- contract test snapshot 의 의도적 갱신은 breaking change catalog row 를 인용하고 PR 에 `intent:breaking-change-approved` label 부여로만 통과.
|
||
|
||
### 6. 위임된 tool
|
||
|
||
> **Trace**: D5 (vulnerability scan). 본 branch 는 **gate wiring owner** — 아래 gate 의 *실행/release-blocking 배선* 은 in-scope 이나, *tool 선택·severity·정책 결정* 은 전용 owner branch 로 위임. 매트릭스의 owner 열이 위임 대상을 가리킨다(단 슬러그 drift 는 §Audit `OWNER_BRANCH_DRIFT` 참조).
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: D5 의 Trivy *tool 선택* 은 본 branch 결정 범위 밖 후보 — 전용 vuln branch 미결. trade-off: 본 branch 는 vuln gate 의 release-blocking 배선만 소유, scanner 선택은 위임/확정 필요(§Audit OWNER_AMBIGUITY).
|
||
|
||
| Gate (wiring in-scope here) | tool/policy 결정 owner (위임) |
|
||
|---|---|
|
||
| vulnerability scan (Trivy) | tool 선택 = [[raw/branch-notes/feature-dependency-vulnerability-management-contract]](미결) / severity 정책 = [[raw/branch-notes/feature-build-release-supply-chain-contract]] |
|
||
| secret scan (gitleaks) | [[raw/branch-notes/feature-secrets-config-source-contract]] |
|
||
| SBOM / Cosign / SLSA / license | [[raw/branch-notes/feature-build-release-supply-chain-contract]] |
|
||
| format / lint (tool + ruleset) | [[raw/branch-notes/feature-static-analysis-quality-contract]] (매트릭스 "(toolchain)" 셀의 실제 owner — parent §2051) |
|
||
| container image scan | [[raw/branch-notes/feature-container-runtime-contract]] |
|
||
| .env.example drift | [[raw/branch-notes/feature-env-driven-runtime-configuration]] |
|
||
|
||
## Gate ↔ Branch Contract Test 소유권 매트릭스
|
||
|
||
모든 gate는 단일 owner branch contract test를 실행. release-blocking 여부 명시.
|
||
|
||
| CI gate | release-blocking | owning branch contract test |
|
||
|---------|------------------|------------------------------|
|
||
| format / lint | true | (toolchain) |
|
||
| unit test | true | test-taxonomy-fixture-contract |
|
||
| architecture test (ArchUnit) | true | architecture-enforcement-rules |
|
||
| envelope/error contract test | true | contract-verification-test-suite (envelope) |
|
||
| log/MDC contract test | true | contract-verification-test-suite (log) |
|
||
| env contract test | true | contract-verification-test-suite (env) |
|
||
| registry contract test | true | contract-verification-test-suite (registry) |
|
||
| OpenAPI drift | true | contract-verification-test-suite (OpenAPI) |
|
||
| schema drift (JSON serialization) | true | contract-verification-test-suite (schema) |
|
||
| integration test (default profile) | true | test-taxonomy-fixture-contract |
|
||
| integration test (optional adapter matrix) | true if matrix enabled | integration-adapter-templates |
|
||
| sample removal smoke | true | sample-removal-adoption-contract |
|
||
| SBOM generation | true | build-release-supply-chain |
|
||
| signed artifact (Cosign) verification | true | build-release-supply-chain |
|
||
| SLSA provenance attestation | true | build-release-supply-chain |
|
||
| vulnerability scan (Trivy) high/critical | true | build-release-supply-chain |
|
||
| license scan (NOTICE compliance) | true | build-release-supply-chain |
|
||
| secret scan (gitleaks) | true | secrets-config-source |
|
||
| .env.example drift | true | env-driven-runtime-configuration |
|
||
| container image scan (Trivy image) | true | container-runtime-contract |
|
||
|
||
> ⚠️ owner 열 슬러그 drift — `build-release-supply-chain` → `feature-build-release-supply-chain-contract`, `secrets-config-source` → `feature-secrets-config-source-contract`, `(toolchain)`(format/lint) → `feature-static-analysis-quality-contract`. 상세·근거는 §Audit & Findings `OWNER_BRANCH_DRIFT`. (사용자 결정 영역이므로 자동 rewrite 하지 않고 정합 권고만 — §구현 가이드 §6 및 §엣지·실패·의존 의존 목록은 정합된 슬러그 사용.)
|
||
|
||
## Gate Matrix (deprecated)
|
||
|
||
> CI Gate 전체 목록과 owner branch 매핑은 위 "Gate ↔ Branch Contract Test 소유권 매트릭스"가 SSOT. 별도 Gate Matrix 양식은 deprecated.
|
||
|
||
## 테스트 계약
|
||
|
||
- contract test result gate: GitHub Actions matrix에서 `contract-test` job의 status가 `failure`이면 workflow status도 `failure`여야 함. 측정 방법: workflow yaml의 `needs: [contract-test]` 의존성 + `if: success()` gate 명시 verify. 누락 시 fail.
|
||
- OpenAPI drift gate: `openapi-diff` 또는 동등 도구를 `openapi-snapshot.yaml` (checked-in baseline) vs build 시 generated OpenAPI과 비교. diff 결과에 breaking change가 1건이라도 있으면 release-block. 측정 방법: CI step `./gradlew openapiCheckSnapshot` exit code 0 verify.
|
||
- high/critical vulnerability 차단 정책이 없으면 실패.
|
||
- sample removal smoke gate: workflow yaml에 `sample-removal-smoke` job이 정의되고 release-blocking matrix에 포함되어 있어야 함. 측정 방법: workflow yaml grep on `sample-removal-smoke` + matrix.profile에 `sample-off` 포함 verify.
|
||
|
||
## 엣지·실패·의존
|
||
|
||
> R4 캡처용. 정상 경로 외에 *구현 중 부딪힐* 실패/엣지/다른 계약 의존을 미리 열거. 각 실패 경로는 §Claims To Verify 항목과 1:1 대응(검증 위임).
|
||
|
||
### 실패·엣지 경로
|
||
|
||
- **needs/if fan-in status 전파**: 실패한 matrix job 이 release job 으로 failure 를 전파하는가 — `CIGG-C3` 가 `if: always()` 등 정확한 fan-in 규칙 미보장. 기대: gate 1건 실패 → release block (§Claims C1).
|
||
- **oasdiff exit code semantic**: breaking change 발생 시 `oasdiff breaking` 이 non-zero exit 인가 — `CIOS-C5` 미보장. 기대: breaking 1건 → exit != 0 → CI fail (§Claims C2).
|
||
- **Trivy false negative / suppression bypass**: CVE DB 갱신 지연, 또는 무단 `trivy-ignore` 추가로 우회. 기대: known CVE → fail, 무단 suppression PR review 없이 차단 (§Claims C3).
|
||
- **flaky 14d sunset 자동 강제 부재**: `@QuarantinedSince` 류 annotation 없으면 14일 초과를 감지할 수 없음. 기대: 14일 초과 → build fail (§Claims C5).
|
||
- **label escape-hatch 무단 사용**: label 부여 권한 정책 부재 시 누구나 `intent:breaking-change-approved` 로 우회. 기대: branch protection + CODEOWNERS 로 권한 제한 (§Claims C6).
|
||
- **gate matrix ↔ 실제 contract test 불일치**: 20행 표의 owning branch 가 실제 contract test 와 어긋남(슬러그 drift 포함, §Audit). 기대: lint script 로 표 ↔ 코드 cross-check (§Claims C7).
|
||
|
||
### 다른 계약 의존 (delegated owner = consume 대상)
|
||
|
||
> 본 branch 는 아래 owner branch 의 contract test 를 release-blocking gate 로 consume 한다. 해당 계약이 바뀌면 본 branch 의 gate 실패 조건이 변동된다 (R4 IMPLICIT_DEPENDENCY 명시).
|
||
|
||
- [[raw/branch-notes/feature-contract-verification-test-suite]] — envelope/log/env/registry/OpenAPI/schema contract test 를 gate 로 consume.
|
||
- [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] — unit/integration test gate + flaky quarantine **consumer**(D9: 본 branch 가 quarantine SSOT).
|
||
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — ArchUnit architecture test gate.
|
||
- [[raw/branch-notes/feature-build-release-supply-chain-contract]] — SBOM/Cosign/SLSA/license/vulnerability-severity gate (severity 정책 owner).
|
||
- [[raw/branch-notes/feature-secrets-config-source-contract]] — secret scan (gitleaks) gate.
|
||
- [[raw/branch-notes/feature-env-driven-runtime-configuration]] — .env.example drift gate.
|
||
- [[raw/branch-notes/feature-container-runtime-contract]] — container image scan gate.
|
||
- [[raw/branch-notes/feature-integration-adapter-templates]] — optional adapter test matrix.
|
||
- [[raw/branch-notes/feature-sample-removal-adoption-contract]] — sample removal smoke gate.
|
||
- [[raw/branch-notes/feature-static-analysis-quality-contract]] — format/lint tool + ruleset (매트릭스 "(toolchain)" 셀의 실제 owner; 본 branch 는 threshold/gate wiring 만).
|
||
- [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] — vulnerability scanner *tool 선택*(D5 위임 후보, 현재 미결).
|
||
|
||
## 검증해야 할 주장
|
||
|
||
> 공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리.
|
||
|
||
| Claim | Why uncertain | How to verify | Status |
|
||
|---|---|---|---|
|
||
| GitHub Actions `needs:` + `if: success()` 조합이 ca-tmpl 11 release-blocking gate 모두를 강제 | `CIGG-C2`/`C3` 는 매핑 가능성만 보장, `if:` 의 fan-in 시 status 전파 규칙은 인용 범위 밖 | 의도적 fail job 을 matrix 에 추가 → 후속 release job 이 실제로 block 되는지 verify | `partially-implemented` — release-gate 를 `if: always()` + `needs.*.result` 스캔으로 구현(`success()` 단독은 skipped→차단 실패임을 확인). **CI 실제 fail 전파 = `needs-confirmation`** |
|
||
| `openapi-diff` 또는 `oasdiff` 의 exit code 가 breaking change 발생 시 non-zero | `CIOS-C5` 는 breaking 검출 기능만 보장, exit code semantic 명시 없음 | 의도적 breaking change PR 생성 → `oasdiff breaking` exit code != 0 verify | `locally-verified` — ca-tmpl 은 oasdiff 대신 `openapiCheckSnapshot`(Gradle Test, 스냅샷 byte-compare) 채택; 로컬 exit 0 확인. drift 시 fail 은 OpenApiDriftContractTest 가 보장 |
|
||
| Trivy high/critical 차단 정책이 false negative 없이 동작 | Trivy CVE DB 갱신 주기 / suppression 우회 가능성 | 의도적 CVE-known dependency (예: log4j 2.14) 추가 → CI fail verify; `trivy-ignore` 무단 추가가 차단되는지 verify | `delegated` — `dependency-vulnerability.yml`(feature-dependency-vulnerability-management-contract) 소유. 본 branch 는 gate-matrix 에서 release-blocking 으로 배선만 |
|
||
| sample-removal-smoke job 이 release-blocking matrix 에 실제 포함됨 | workflow yaml 의 matrix 구성 검증 부재 | workflow yaml grep on `sample-removal-smoke` + `matrix.profile` 에 `sample-off` 포함 verify | `implemented` — `sample-removal-smoke` 잡 + `strategy.matrix.profile: [sample-off]` 존재, release-gate `needs` 포함. SampleRemovalSmokeContractTest 로컬 통과 |
|
||
| flaky quarantine bucket 의 14일 sunset 이 자동 강제 | sunset deadline 의 자동 감지 메커니즘 부재 가능 | quarantine bucket 의 test 별 `@QuarantinedSince` annotation + CI step 으로 14일 초과 시 build fail verify | `implemented` (`locally-verified`) — `@Tag("quarantine")` + repo-루트 `flaky-quarantine.yaml` + `verifyQuarantineSunset`(check 연결). over-age 170일 positive control fail 확인. (annotation 대신 레지스트리 채택 — §진행 중 메모) |
|
||
| `intent:breaking-change-approved` label escape hatch 가 무단 사용 차단 | label 추가 권한 정책 부재 시 누구나 우회 | branch protection + CODEOWNERS 로 label 권한 제한 + audit log 점검 | `partially-implemented` — `breaking-change-approval` 잡(governed snapshot 변경 시 label 강제) + CODEOWNERS(snapshot/approved 경로). **branch protection "Require Code Owners" 활성화는 운영 설정(미적용) = `needs-confirmation`** |
|
||
| 20개 gate 표의 owning branch 매핑이 실제 contract test 와 일치 | 표만 있고 cross-check 부재 + 슬러그 drift(§Audit) | 각 owning branch 의 contract test 코드 grep + 본 표와 일치 verify (수동 또는 lint script) | `implemented` (`locally-verified`) — `.github/ci-gate-matrix.yml`(20행) + `verify-gate-matrix.sh`. 로컬 PASS(16 verified + 4 delegated-pending). 슬러그는 §Audit 정합본 사용 |
|
||
|
||
## 관심사 커버리지 (coverage-auditor 자동 생성 — seed)
|
||
|
||
> `/coverage` 가 채우는 **생성물**. 아래는 `/branch-spec`(2026-06-15)이 governing doc [[wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]] + parent §538 "CI Quality Gates" 관심사로 seed 한 것 — coverage-auditor 가 검증/정정한다. 상태: `covered-here` / `delegated` / `missing`.
|
||
|
||
| 관심사 (governing §538 CI Quality Gates) | 상태 | owner | 심각도 | 근거 |
|
||
|--------|------|-------|--------|------|
|
||
| format/lint/test/contract/OpenAPI drift/security scan 이 분리된 gate 로 실행 | covered-here | — | — | 매트릭스 20행 + D2/D3 |
|
||
| merge 전 실패 가능 gate vs warning-only gate 구분 | covered-here | — | — | D1, D3, D4 / §구현 §1 |
|
||
| contract violation = release-blocking (warning-only 불가) | covered-here | — | — | D1 |
|
||
| optional adapter test = adapter enabled matrix only | covered-here | — | — | D2 / §구현 §2 |
|
||
| OpenAPI drift ground truth = code-generated snapshot | covered-here | — | — | D6 / §구현 §3 |
|
||
| flaky test quarantine + sunset 정책 | covered-here | — | — | D7, D9 / §구현 §4 |
|
||
| vulnerability scan *tool 선택* | delegated | [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] (미결) | Should-fix | OWNER_AMBIGUITY: 위임 링크 존재, 단 owner 의 scanner 미결 (§Audit) |
|
||
| vulnerability severity → release-block 정책 | delegated | [[raw/branch-notes/feature-build-release-supply-chain-contract]] | OK | §구현 §6 |
|
||
| secret scan (gitleaks) tool | delegated | [[raw/branch-notes/feature-secrets-config-source-contract]] | OK | §구현 §6 / §엣지 의존 |
|
||
| SBOM / Cosign / SLSA / license | delegated | [[raw/branch-notes/feature-build-release-supply-chain-contract]] | OK | §구현 §6 |
|
||
| format/lint *tool + ruleset* | delegated | [[raw/branch-notes/feature-static-analysis-quality-contract]] | OK | parent §2051 / §구현 §6 |
|
||
| container image scan | delegated | [[raw/branch-notes/feature-container-runtime-contract]] | OK | §구현 §6 |
|
||
|
||
## Audit & Findings (2026-06-15 — `/branch-spec` ground-truth 대조)
|
||
|
||
> ca-tmpl `docs/registries/*.yaml` + `src/` + sibling branch-notes 대조 결과. **사용자 작성 결정 영역(매트릭스 owner 열, §완료 후 wiki 추출 대상)은 자동 rewrite 하지 않고 정합 권고만** (CLAUDE.md §15.5 R3, `/branch-spec` §2 drift 규칙). 신규 작성 섹션(§구현 가이드 §6, §엣지·실패·의존, §Coverage)은 정합된 슬러그 사용.
|
||
|
||
- **OWNER_BRANCH_DRIFT** (Gate matrix owner 열):
|
||
- `build-release-supply-chain` → 실제 branch-note 슬러그 `feature-build-release-supply-chain-contract` (존재 확인). 권고: 매트릭스 5개 행(SBOM/Cosign/SLSA/vuln/license) owner 정합.
|
||
- `secrets-config-source` → 실제 `feature-secrets-config-source-contract` (`docs/registries/secrets-classification.yaml` `owner_branch` SSOT 와 일치). 권고: secret scan 행 정합.
|
||
- `(toolchain)` (format/lint 행) → 실제 owner `feature-static-analysis-quality-contract` (parent §2051: "tool 선택 + 룰셋" owner; 본 branch 는 *threshold/gate wiring* 만). 권고: owner 명시.
|
||
- **OWNER_AMBIGUITY** (D5 — vulnerability scanner tool 선택): scanner *tool 선택* 의 owner 미확정. 전용 `feature-dependency-vulnerability-management-contract` 는 현재 scanner 미결, `feature-build-release-supply-chain-contract` 는 severity 정책만 소유. 권고: 본 branch 는 vuln gate 의 release-blocking 배선만 유지하고, Trivy *tool 선택* 결정은 dependency-vulnerability 또는 supply-chain owner 로 위임/확정.
|
||
- **EXTRACTION_TARGET_DRIFT** (§완료 후 wiki 추출 대상): 지정 경로 `wiki/projects/ca-skeleton-operational-contract.md` 는 wiki 파일로 존재하지 않음(그 슬러그는 `raw/project-notes/`). 실제 CI canonical = [[wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]] (§CI `documented-only`, line 41~47). 권고: 추출 대상 정합.
|
||
- **NO_CI_WORKFLOW** (ground truth, non-blocking): ca-tmpl 에 `.github/workflows/` 부재 → 본 branch 의 모든 gate 는 `documented-only`/`planned` 단계. 노트 self-report(§Cluster, parent §CI documented-only)와 일치 — `actually-implemented` 과장 없음. drift 아님, 현황 기록.
|
||
|
||
## 완료 후 wiki 추출 대상
|
||
|
||
- `wiki/projects/ca-skeleton-operational-contract.md`의 CI quality gate canonical section. (⚠️ 경로 drift — 실제 canonical 은 [[wiki/projects/ca-tmpl/devops-ci-supply-chain-dx]], §Audit `EXTRACTION_TARGET_DRIFT` 참조.)
|
||
|
||
## 마주친 문제
|
||
|
||
- 2026-06-20: 구현 중 회피한 함정 3종 — (1) `if: success()` fan-in aggregator 는 상위 실패 시 *skipped*(차단 아님) → `always()`+result 스캔으로 전환; (2) `/docs` 전체 gitignore → CI-read 신규 파일을 tracked 경로로(레지스트리 = repo 루트, gate-matrix = `.github/`); (3) Gradle 빈 tag 버킷 Test 실패 → `failOnNoDiscoveredTests=false`. 상세: [[raw/errors/ci-fan-in-skipped-not-failed-and-gitignored-config-2026-06-20]].
|
||
- 2026-06-20 (CI 실관측 + 사용자 결정): 사용자가 워크플로를 실제 CI 러너에서 돌려 `verifyEnvKeys: missing docs/registries/env-keys.yaml` → `BUILD FAILED` 확인. 핵심 트레이드오프 부상 — registry 게이트를 "부재 시 skip" 으로 완화하면 CI green 이지만 **CI 에서 vacuous**(계약 미강제) → 본 branch 목표("계약을 CI 에서 강제")와 정면 충돌. docs 읽는 contract 테스트 18/21 이 이미 skip-tolerant, verifyEnvKeys 만 throw outlier 임을 확인. 사용자에게 옵션 제시 → **Option 1: registries 커밋** 채택. `.gitignore` 를 `/docs/*` + `!/docs/registries/` 로 좁혀 운영 레지스트리 7개만 추적(나머지 `/docs` 는 private 유지). `verifyEnvKeys: OK — 99 env keys / 74 required placeholders / 84 APP_ keys` 재확인. 게이트가 fresh checkout 에서 실제 강제됨 = `locally-verified`(CI 재실행 `needs-confirmation`).
|
||
- 2026-06-20 (CI 3차 — **quarantine 메커니즘 첫 실사용**): full `check` 에서 `PrivacySettingsTest.blankSalt_warnsAndFallsBackToDevSentinel(CapturedOutput)` 1건만 실패(487 tests, 1 failed) → release-gate 가 다시 정확히 차단(`quality-gates: failure` → `::error::release-gate`), **Claim C1 재실증**. 원인(증거): `CapturedOutput` 이 JVM-전역 async logback appender(`logback-spring.xml:23` `ASYNC_ENABLED` defaultValue=**true** → `:126` `MetricsAsyncAppender` on root)와 race — sibling `@SpringBootTest`(ActuatorSecurityHttpTest 등 4종)가 그 async appender 를 설치하면, 경량 `ApplicationContextRunner` 테스트의 `log.warn` 이 worker 스레드에서 `output.getOut()` 읽은 *뒤* flush → 단언 실패. 순서/타이밍 의존(로컬 단독·full 모두 통과 = 이기는 순서, CI = 지는 순서). 처리: 사용자 결정 **격리(quarantine)** — flaky 한 `blankSalt` 메서드에만 `@Tag("quarantine")`(realSalt 는 경고 미발생이라 async 무관, 제외) + `flaky-quarantine.yaml` 등록(reason + tracking_issue(TODO, 머지 전 실 Gitea 이슈로 교체) + `quarantined_since: 2026-06-20`, sunset 2026-07-04). 검증: `verifyQuarantineSunset: OK — 1 registered, 1 tagged`, drift guard simple-name suffix 매칭(`build.gradle:670`) 정합, `:app-bootstrap:test` 전체 BUILD SUCCESSFUL(flaky 제외), `quarantineTest` 가 1건 비차단 실행(`tests=1 failures=0`), `check verifyPublicPathSnapshot` BUILD SUCCESSFUL. **잠복 위험**: 같은 모듈 `LoggingSettingsTest`(badTimezone/badAsyncQueueSize `warnsAndFallsBack`)도 동일 CapturedOutput+async race 패턴 — 이번엔 미발생이나 다른 순서에서 재현 가능. 근본수정(sunset 내 owner 몫): `logback-test.xml` 로 test 시 async 비활성, 또는 `ListAppender` 직접 단언으로 stdout race 제거 — 한 번에 이 클래스 전체 flake 해소.
|
||
|
||
## 묶음
|
||
|
||
<!-- GENERATED: sources:start -->
|
||
- [[raw/company-tech-blogs/ci-flaky-test-quarantine-spotify-google]]
|
||
- [[raw/official-docs/ci-github-actions-vs-gitlab-comparison]]
|
||
- [[raw/official-docs/ci-openapi-snapshot-diff-tooling]]
|
||
- [[raw/official-docs/dx-mise-asdf-tool-versioning]]
|
||
- [[raw/official-docs/dx-testcontainers-java-best-practices]]
|
||
- [[raw/official-docs/supply-chain-cosign-keyless-sigstore]]
|
||
- [[raw/official-docs/supply-chain-slsa-provenance-framework]]
|
||
<!-- GENERATED: sources:end -->
|
||
|
||
<!-- GENERATED: interviews:start -->
|
||
- [[raw/interviews/ci-release-gate-fan-in-blocking-2026-06-20]]
|
||
<!-- GENERATED: interviews:end -->
|
||
|
||
<!-- GENERATED: errors:start -->
|
||
- [[raw/errors/ci-fan-in-skipped-not-failed-and-gitignored-config-2026-06-20]]
|
||
<!-- GENERATED: errors:end -->
|
||
|
||
<!-- GENERATED: blog-topics:start -->
|
||
- [[raw/blog-topics/ci-gate-wiring-vs-policy-ownership-2026-06-20]]
|
||
<!-- GENERATED: blog-topics:end -->
|
||
|
||
> 2026-06-20 구현 단계에서 errors / blog-topics / interview-prep 파생 자료 누적.
|
||
|
||
### 오류 기록 (본 feature 작업 중 발생)
|
||
|
||
- [[raw/errors/ci-fan-in-skipped-not-failed-and-gitignored-config-2026-06-20]] — fan-in skip 함정 + gitignored 설정 CI 의존 + 빈 tag 버킷.
|
||
|
||
### Blog topics (이 작업에서 나온 글감)
|
||
|
||
- [[raw/blog-topics/ci-gate-wiring-vs-policy-ownership-2026-06-20]] — gate wiring vs policy 소유권 분리 + quarantine sunset 강제.
|
||
|
||
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
||
|
||
- [[raw/interviews/ci-release-gate-fan-in-blocking-2026-06-20]] — fan-in 으로 release 차단을 *보장* 하는 법.
|
||
|
||
## 관련 일일 노트
|
||
|
||
> 이 브랜치를 작업한 날짜들. 양방향 nav 유지.
|
||
|
||
- 2026-06-20 — gate wiring 구현(워크플로 + gate-matrix + 크로스체크 + flaky quarantine sunset + escape-hatch 거버넌스). documented-only → actually-implemented(`locally-verified`; CI 실행 `needs-confirmation`).
|
||
- 2026-06-20 (후속) — CI 실관측으로 verifyEnvKeys 실패 → docs/registries 커밋(gitignore 좁힘, 사용자 Option 1)으로 registry 게이트 CI 강제 회복. PR 템플릿 한국어화.
|
||
- 2026-06-20 (CI 속도 최적화 — 사용자 결정 "gradle 잡 통합"): CI wall-clock ~10분+ 원인 = 게이트별 잡 분리로 단일 self-hosted 러너가 잡마다 checkout+setup-java+Gradle캐시+재컴파일 반복(특히 `quality-gates`의 `check` 5m14s 외에 openapi-drift/sample-removal/security-snapshot이 check가 *이미 실행하는* 테스트를 재실행, optional-adapter-matrix는 테스트 1개에 app-bootstrap 테스트를 4× 재컴파일). 해결: gradle 잡 4개 제거하고 `./gradlew check verifyPublicPathSnapshot` 단일 invocation으로 통합(9잡→5잡, gradle 잡 6→2). 게이트 강도 불변(check가 전 테스트 실행, gate-matrix-lint가 매트릭스↔코드 정합 유지). 매트릭스의 optional-adapter 행 mechanism을 workflow-job→contract-test(OptionalAdapterConditionalExecutionContractTest, check 내 실행)로 정합. gate-matrix-lint PASS(20=16+4) 유지.
|
||
- 2026-06-20 (CI 2차 — 게이트 배선 검증 성공 + 2차 수정): release-gate fan-in 이 `quality-gates: failure` + `breaking-change-approval: failure` 를 정확히 감지·차단(`::error::release-gate: ... failed`) → **Claim C1 실증 완료**. 두 실패 모두 원인 규명·수정: (1) registries 만 커밋해 `docs/runbooks/` 부재 → Runbook/BackgroundJobErrorCode 계약 5건이 skip→fail(runbook 파일 dangling). `mv docs/runbooks` 로 로컬 재현 후 gitignore 에 `!/docs/runbooks/` 추가(45개 runbook 추적). (2) breaking-change governed 정규식에 `ci-gate-matrix.yml`(config)을 과포함 → 매트릭스 생성 PR 이 라벨 강요당함. governed 를 OpenAPI 스냅샷·`*.approved.*` 로 한정(config 는 CODEOWNERS+lint 로 보호). checkstyleTest ERROR 대량은 비차단 노이즈(static-analysis branch 소유, `ignoreFailures=true`) — 본 branch 실패 원인 아님.
|
||
- 2026-06-20 (CI 3차 — quarantine 첫 실사용): full `check` 에서 `PrivacySettingsTest.blankSalt`(CapturedOutput) 1건 flaky 실패 → release-gate 재차단(Claim C1 재실증). 원인 = async logback(`logback-spring.xml` ASYNC_ENABLED 기본 true) + sibling `@SpringBootTest` 가 설치한 JVM-전역 appender 와의 stdout race. 사용자 결정 **격리**: `blankSalt` 메서드만 `@Tag("quarantine")` + `flaky-quarantine.yaml` 등록(14d sunset). `verifyQuarantineSunset OK(1/1)`, `quarantineTest` 1건 비차단 실행, `check verifyPublicPathSnapshot` BUILD SUCCESSFUL. 잠복: `LoggingSettingsTest` 동일 패턴. 근본수정(owner): `logback-test.xml` async-off 또는 ListAppender 단언. 상세 → [[raw/errors/ci-fan-in-skipped-not-failed-and-gitignored-config-2026-06-20]] (Trap 4 추가).
|
||
|
||
## 완료 후 정리
|
||
|
||
> 머지/종료 시점에 채움.
|
||
|
||
- PR 링크:
|
||
- 리뷰 메모:
|
||
- 머지 결과 / 배포 환경:
|
||
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
||
- `actually-implemented` 항목:
|
||
- `locally-verified` 항목:
|
||
- `prod-verified` 항목:
|
||
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|