ADR-0036: 제품 버전 체계 — SemVer 단일 소스 + 기계 동기화
- 상태: 승인
- 날짜: 2026-08-04
- 관련: #2860 (Phase 0 구현), 온프렘 버전 관리·버전별 문서·업데이트 배포 전략 (후속 Phase 1/2)
배경
GenD 에는 제품 버전이 존재하지 않았다:
- git tag 0개 — 릴리스 기준점 없음.
- 컴포넌트 버전 제각각 (api 0.1.0 / ui·cli 0.2.0 / relay 0.1.0), 서로 무관.
- 배포 이미지 태그
0.2.<날짜>-<sha>의0.2는 CI 워크플로 하드코딩 리터럴 — 어떤 버전 파일과도 연동되지 않음. - UI 는 로그인 페이지(
__APP_VERSION__=ui/package.json)와 StatusBar (/health의 api__version__)가 서로 다른 버전을 표시.
온프렘 고객사(신복위 등)별 설치 버전이 갈라지기 시작하면 "어느 버전에 어느 기능/가이드가 적용되는가", "업데이트로 무엇이 바뀌는가"에 답할 수단이 없다.
GenOS 실측 반면교사: 버전 문자열이 admin-front/.env.production 과
admin-api/static/version.ini 두 곳에 수기 이중 관리라, 문서는 v1.9.3 까지
나갔는데 제품 표시는 v1.9.1 로 이미 드리프트했다. 사람이 두 곳을 고치는 체계는
반드시 어긋난다.
결정
- 단일 제품 버전 = SemVer
X.Y.Z, 저장소 루트VERSION파일이 단일 소스. 컴포넌트(api/cli/relay pyproject +__init__.__version__, ui/package.json)는scripts/sync_version.py로 기계 동기화하며, CI (lint-doc-counts잡의--check스텝)가 드리프트 시 머지를 차단한다. 패턴이 정확히 1회 매치하지 않으면 통과가 아니라 에러다 (vacuous pass 차단). - 릴리스 = main 의 annotated git tag
vX.Y.Z. 첫 태그는 v1.0.0. - 이미지 태그 파생: main 상시 배포는
<VERSION>-dev.<YYYYMMDD>-<sha7>(예:1.0.0-dev.20260804-a27c0f4) — "고객 출하 버전"과 "내부 최신"이 태그만 봐도 구분된다. 빌드 sha/일시는--build-arg→ ENV(GEND_BUILD_SHA/GEND_BUILD_DATE) 로 이미지에 새긴다. - 런타임 노출:
GET /version(공개, DB 무접근) 이 제품 버전 + 빌드 메타데이터를 반환. UI StatusBar 버전은 릴리스 노트로 링크. - 릴리스 노트:
docs-site/docs/appendix/release-notes.md에## vX.Y.Z (YYYY-MM-DD)규격으로 버전 단위 기록. 내부docs/트리의 구본 사본은 폐기 (docs-site 가 유일 소스).
기각한 대안
- CalVer (
2026.08) — 온프렘 고객에게 hotfix(patch)와 기능(minor)의 위험도 구분을 전달할 수 없다. 지원 정책("최신 + 직전 N개 minor 백포트")도 SemVer 전제를 요구한다. - 컴포넌트별 독립 버전 유지 — GenD 는 단일 제품으로 출하되며 api/ui 가 따로 배포되는 일이 없다. 독립 버전은 "고객 설치본 = 버전 목록 N개"를 만들어 지원 문의·문서 배지를 조합 폭발시킨다.
- git tag 를 버전 소스로 파생 (
git describe) — 이미지 빌드·로컬 dev· worktree 등 태그가 없는 컨텍스트에서 버전이 사라지거나 달라진다. 파일 소스가 결정적이고, 태그는 릴리스 확정 행위로 분리한다. - GenOS
version.ini방식 (서버 JSON 수기 관리) — 배경의 드리프트 실측 그대로. 릴리스 노트 데이터화(in-app 노출)는 Phase 1 에서 릴리스 파이프라인이 생성하는 산출물로 도입한다.
결과
- 버전 관련 표면(로그인 푸터·StatusBar·OpenAPI·
/health·/version·MCP serverInfo·CI/수동 런북 이미지 태그)이VERSION하나에서 파생된다. 예외: Kind/Azure 부트스트랩 스크립트(infra/azure-deploy/scripts/,infra/image-tags.env)의 하드코딩 태그는 이번 스코프 밖 — Phase 1 에서 VERSION 파생으로 통일한다. - 외부 라우팅:
/version은/health처럼 ingress(Exact) 화이트리스트에 등재해야 한다 (catch-all 은 UI SPA 로 흡수). prod/Kind ingress + Gateway API HTTPRoute 에 반영됨. - 릴리스 노트가 버전 단위 규격을 갖춰, 온프렘 고객이 "내 설치본(vX.Y.Z)에 무엇이 들어있나"를 확인할 수 있는 기반이 된다.
- 후속 (이슈 #2860 스코프 밖):
- Phase 1 (#2864 구현) — tag-trigger 릴리스 워크플로
release.yml(정확 태그vX.Y.Z이미지 4종 + GitHub Release, 본문은 release-notes.md 해당 섹션 추출 — 섹션 없으면 릴리스 실패), 온프렘alembic upgradeJob (infra/onprem/, 기존 prod 비적용 — 런북 경고 참조), 문서 "(vX.Y+)" 적용 버전 표기, 부트스트랩 스크립트 태그 VERSION 파생 통일. CHANGELOG 초안 자동 생성(git-cliff)은 Phase 2 로 이월 — 릴리스 게이트는 수기 규격 노트의 존재 검증으로 충분하다고 판단. - Phase 2 (#2868 부분 구현) — 에어갭 번들(
make_airgap_bundle.sh/load-airgap-images.sh— zarf 대신 tar+스크립트 표준, 고객 도구 의존 최소화·수요 발생 시 zarf 재평가), 지원 정책(N-2 보안 백포트·minor 순차 업그레이드) 문서화 완료. 잔여: 출하 minor 단위 Docusaurus versioned docs 스냅샷(유지 ≤3 — v1.1.0 출하 시점 활성화), 기존 prod alembic 이력 정합화(#2869), git-cliff CHANGELOG 초안.
- Phase 1 (#2864 구현) — tag-trigger 릴리스 워크플로