Skip to content

ci(release): release-guard — 수동 릴리스·태그 오지정 탐지 가드 - #30

Merged
baekchangjoon merged 1 commit into
mainfrom
feat/release-guard
Jul 27, 2026
Merged

ci(release): release-guard — 수동 릴리스·태그 오지정 탐지 가드#30
baekchangjoon merged 1 commit into
mainfrom
feat/release-guard

Conversation

@baekchangjoon

Copy link
Copy Markdown
Collaborator

배경

v1.4.0 릴리즈 사고: gh release create로 수동 생성되어 자산이 14개 중 1개뿐이었고, 태그는 소스 버전이 1.3.0인(기능도 없는) 커밋을 가리켰다. 이 PR은 그 유형의 사고를 자동 탐지하는 가드를 추가한다.

변경 내용

.github/scripts/release-guard.sh (신규) — 태그 체크아웃 기준 검증:

  • build.gradle.kts 소스 버전 == 태그 버전 (v1.4.0 사고 유형)
  • maven-plugin/pom.xml<version> + <pjacoco.agent.version> == 태그 버전 (1.2.0 사고 유형)
  • 릴리즈 자산 완전성: agent + testkit 4종 + maven-plugin jar, jar마다 .sha256, 고아/정체불명 자산 없음 (deprecated alias는 선택 — v1.4.x 이후 제거 계획과 정합)

.github/workflows/release-guard.yml (신규) — 사람/PAT이 만든 v* 태그 push, release publish/edit에 반응 + workflow_dispatch 재검증. GITHUB_TOKEN이 만든 이벤트는 워크플로를 발화시키지 않으므로 이 경로는 정확히 "수동 릴리스" 탐지망.

.github/workflows/release.yml — ① 릴리즈 생성 직후 같은 스크립트를 자체 검증 단계로 실행(정상 경로 커버), ② version 입력 regex를 semver로 강화(가드와 태그 계약 일치), ③ 멀티라인 입력 방지용 [[ =~ ]] 전체 문자열 매칭.

docs/RELEASING.md — 가드 2경로 문서화.

검증

  • actionlint + shellcheck 통과
  • 스크립트 시나리오 테스트 9종 (로컬, 실데이터 + fake gh shim):
    • T1 실제 v1.4.0(14 자산) → PASS
    • T2 소스 버전 불일치 / T3 자산 누락(v1.4.0 사고 재현) / T4 정체불명 자산 / T5b 릴리즈 없음 / T6 태그 형식 오류 / T7 멀티라인 태그 주입 / T8 gh 401(404 아님) / T9 pjacoco.agent.version 불일치 → 모두 FAIL(exit 1)
    • T5a 태그 push 시 릴리즈 아직 없음 → NOTE 후 스킵(레이스 허용)
  • 제품 코드 무변경(.github/ + docs만) — 프로젝트 테스트 스위트에 영향 없음
  • 코드 리뷰(spec-compliance + 품질) 완료: 지적 10건 중 8건 반영(GITHUB_TOKEN 트리거 억제, 태그 regex 우회, gh 오류 혼동, released 중복 이벤트 등), 2건 근거와 함께 거절(alias 필수화 — release.yml 자체 개수 가드가 이미 강제; CI에 bats 추가 — 별도 스코프)

한계

GitHub에는 릴리스 생성을 차단하는 수단이 없어 이 가드는 탐지 컨트롤이다 — 위반은 빨간 Actions 실행으로 드러난다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FavJfhdcNAHZKWvmSBVLYZ

Validation script (.github/scripts/release-guard.sh) checks, at the tag:
- build.gradle.kts source version == tag version (the v1.4.0 incident:
  tag on a commit whose source still said 1.3.0)
- maven-plugin/pom.xml <version> AND <pjacoco.agent.version> == tag
  version (the 1.2.0 incident: agent version missed on bump)
- the release carries the full release.yml asset set: agent + 4 testkit
  + maven-plugin jars, a .sha256 per jar, no orphans, no stray assets;
  the deprecated jacocoagent-parallel alias stays optional so its
  planned removal after v1.4.x needs no guard change

Wired in two paths, because events created with GITHUB_TOKEN do not
trigger other workflows:
- release.yml runs the script as a self-check right after publishing
  (covers every workflow-produced release)
- the standalone release-guard workflow fires on human/PAT-created v*
  tag pushes and release publish/edit — exactly the manual-release case
  being guarded — plus workflow_dispatch for re-validation

Hardening from review: whole-string [[ =~ ]] tag validation (grep -E is
line-based and a multi-line value could smuggle a second GITHUB_OUTPUT
key into the checkout ref), gh api 404 vs auth/network failure split so
outages can't silently pass the asset check, release.yml version input
tightened to semver so the two workflows share one tag contract.

GitHub cannot block release creation, so this is a detective control: a
bad release turns into a red run instead of shipping silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FavJfhdcNAHZKWvmSBVLYZ
@baekchangjoon
baekchangjoon merged commit 9fd4605 into main Jul 27, 2026
8 checks passed
@baekchangjoon
baekchangjoon deleted the feat/release-guard branch July 27, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant