본문으로 건너뛰기

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.productionadmin-api/static/version.ini 두 곳에 수기 이중 관리라, 문서는 v1.9.3 까지 나갔는데 제품 표시는 v1.9.1 로 이미 드리프트했다. 사람이 두 곳을 고치는 체계는 반드시 어긋난다.

결정

  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 차단).
  2. 릴리스 = main 의 annotated git tag vX.Y.Z. 첫 태그는 v1.0.0.
  3. 이미지 태그 파생: main 상시 배포는 <VERSION>-dev.<YYYYMMDD>-<sha7> (예: 1.0.0-dev.20260804-a27c0f4) — "고객 출하 버전"과 "내부 최신"이 태그만 봐도 구분된다. 빌드 sha/일시는 --build-arg → ENV(GEND_BUILD_SHA/ GEND_BUILD_DATE) 로 이미지에 새긴다.
  4. 런타임 노출: GET /version (공개, DB 무접근) 이 제품 버전 + 빌드 메타데이터를 반환. UI StatusBar 버전은 릴리스 노트로 링크.
  5. 릴리스 노트: 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 upgrade Job (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 초안.