Skip to content

릴리스 브랜치 전략 재설계 — dev→preview→main 전 구간 ff 승격 + dev 기본 브랜치 전환 (#466 후속) #480

Description

@parkjs101

배경

#466은 squash 승격이 스스로 ancestor 가드를 깨뜨리는 문제였고, #468이 승격 후 자동 재정렬로 해소했다. 이후 v2.17.18/20/21/22 네 사이클이 정상 승격됐다.

이 이슈는 그 수정이 남긴 부채와, 그것을 없애는 구조 변경에 관한 것이다.

문제 1 — 재정렬이 쌍둥이 커밋을 만든다

realign_branch_onto_maindevpreview각각 머지 커밋을 만든다. 두 커밋은 부모도 트리도 같은데 SHA만 다르다.

ad223a05 (dev)      parents=b691e6ff 8db06a5b  tree=fd6b3379
c477f9e1 (preview)  parents=b691e6ff 8db06a5b  tree=fd6b3379
→ NOT ancestors of each other

baa40b45/c660dfda(v2.17.20)도 동일한 쌍이다. 서로가 서로의 조상이 아니므로 dev와 preview가 영구히 갈라진다.

현재 누적 8개, 그리고 지금 이 순간:

main subset preview: OK
preview subset dev: BROKEN      ← 쌍둥이 때문

문제 2 — 근본 원인은 그대로다

promote-to-main.sh:128 은 여전히 --squash 다. stable 범프를 별도 승격 브랜치에서 만들어 접어 넣으므로 main에 고유 커밋이 생기고, 그래서 재정렬이 필요하다. #468은 증상을 자동으로 갚아줄 뿐 원인을 없애지 않는다.

제안 — 전 구간 fast-forward

1. codex/* → dev (PR, ci-aggregate 필수)
2. dev에서 X.Y.Z-preview.TS 범프
3. git push origin dev:preview      (ff) → npm preview
4. preview에서 suffix 제거 → X.Y.Z
5. git push origin preview:main     (ff) → npm latest
6. git push origin preview:dev      (ff) dev 따라잡기

4번이 핵심이다. stable 범프를 승격 브랜치가 아니라 preview 위에서 만들면 main 고유 커밋이 사라지고, 조상 관계가 정의상 유지된다. 6번으로 dev가 preview를 흡수하므로 쌍둥이가 불필요해진다.

세 브랜치는 유지한다. publish.yml:256 이 브랜치와 태그 짝을 강제하므로(preview:preview, latest:main) preview 브랜치는 beta 발행에 반드시 필요하다.

삭제 가능해지는 것

  • realign_branch_onto_main (promotion-checkout.sh)
  • publish.ymlcertified-sha + 트리 해시 대조(:71-97) — ff면 main HEAD == 검증된 SHA라 CI 증거가 그대로 이전됨
  • AGENTS.md 재정렬 절과 reset --hard 처방 (dev에 새 커밋이 있을 때 파괴적)

Branch protection

main preview dev
required checks ci-aggregate ci-aggregate ci-aggregate
strict false false false
force push / deletion 금지 금지 금지
enforce_admins false false false
PR 필수 아니오 아니오

현재 main 만 보호돼 있고(ci-aggregate, non_admins) preview/dev는 무방비다.

  • strict: false 필수 — true면 ff push가 거부된다
  • platform-aggregate 는 required 금지 — postinstall-platform.ymlpull_request 트리거에 paths 필터가 있어 인스톨러 표면을 안 건드리는 PR에는 생성되지 않고, 그러면 PR이 "Expected — Waiting for status"로 영구 대기한다 (docs(readme): let the release badge read the release #352 실제 전례)
  • main/preview에 PR 미요구 — 승격이 ff push이기 때문. 검증이 사라지는 게 아니라 PR 시점에서 SHA 단위로 이동한다. 승격 대상 SHA는 dev에서 이미 ci-aggregate 를 통과했고 ff는 내용을 바꾸지 않으므로, main HEAD가 검증된 SHA와 동일해져 오히려 지금보다 강해진다
  • enforce_admins: false — ff가 push인 이상 불가피. #333도 같은 이유로 false를 택했다. 실질 방어는 npm publish 게이트(expected-sha 필수, exact-SHA CI 확인, 브랜치:태그 짝)가 담당하며 이 이슈에서 손대지 않는다

dev를 기본 브랜치로 전환

maindev 로 default branch 변경. 새 PR이 자동으로 dev를 base로 잡아 base 실수를 없앤다. 대가로 GitHub 첫 화면이 dev가 된다.

단, #333이 "dev 는 릴리스 경로 밖의 contributor 통합 base로 유지"라고 명시 결정한 바 있다. 이 제안은 dev를 릴리스 경로 안으로 넣으므로 그 판단을 뒤집는다. 결정권자 확인이 필요하다.

핫픽스 경로 — 추천안

ff 구조는 preview가 항상 릴리스 가능하다고 전제하는데, 현재 preview에는 미출시 커밋이 51개 쌓여 있다.

main..preview 51    preview..main 0

이 상태에서 main만 급히 고쳐야 하면 ff 승격은 51개를 전부 함께 내보낸다. 골라낼 수 없다.

추천: hotfix/* → main → dev/preview 백머지 (아래 1번).

51커밋 격차를 보면 긴급 수정이 필요해질 상황이 실제로 보이고(#428이 그런 사례였다), 이 경로의 유일한 위험인 백머지 누락은 승격 스크립트에 사후 검증(main ⊂ preview 확인)을 넣어 기계적으로 막을 수 있다. 그 검증이 없으면 #466과 같은 결함 클래스가 재발한다.

검토한 선택지:

  1. hotfix/* → main → dev/preview 백머지 ← 추천. 표준 경로. main에 preview가 모르는 커밋이 잠시 생기므로 백머지 + 사후 검증이 필수
  2. 핫픽스도 정규 경로로 — preview 전체를 함께 출시. 단순하지만 긴급 수정이 미검증 기능 51개를 끌고 나간다
  3. 핫픽스 없음 — preview를 항상 출시 가능 상태로 유지. 규율 부담이 크다

마이그레이션

릴리스가 정상 작동 중이므로 급하지 않다. 조용한 시점에:

  1. 보호 적용 (preview/dev 신규, main strict:false 확인), default branch → dev
  2. 스크립트 수정 — release-preview.sh preview push, promote-to-main.sh squash→ff, realign 제거
  3. publish.yml certified-sha 경로 제거
  4. test.yml/postinstall-platform.yml push 트리거 확인
  5. 누적된 쌍둥이 8개 정리 — dev/preview를 한 줄기로 재정렬
  6. AGENTS.md/README/structure/ 동기화
  7. 핫픽스 경로 문서화 + 승격 스크립트에 main ⊂ preview 사후 검증 추가

측정 시점 2026-08-26, main 8db06a5b (v2.17.22) / preview c477f9e1 / dev 28b89527. 조사는 전부 read-only.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciCI/CD pipelinepriority:P1Next up after stabilization

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions