Skip to content

운영 릴리즈 버전관리 자동화#611

Merged
Hyeonjun0527 merged 3 commits into
developfrom
chore/prod-version-management
May 17, 2026
Merged

운영 릴리즈 버전관리 자동화#611
Hyeonjun0527 merged 3 commits into
developfrom
chore/prod-version-management

Conversation

@Hyeonjun0527

@Hyeonjun0527 Hyeonjun0527 commented May 17, 2026

Copy link
Copy Markdown
Member

Summary

  • main 운영 배포 전용 version-management rule/docs/agent skill SSOT를 추가했습니다.
  • frontend-only 배포는 PR intent(release:patch|minor|major)로 frontend version/image를 계산하고, 최신 releases/ backend/DB 상태와 합쳐 최종 release record를 남기게 했습니다.
  • backend-only 배포는 backend가 repository_dispatch(backend-prod-deployed) payload를 보내면, frontend repo가 FE(current)+BE(new) atomic 조합으로 releases/prod-*.yaml을 기록하게 했습니다.
  • develop/test-server 배포 workflow는 변경하지 않았습니다.

Release contract

  • 허용 release intent는 release:patch, release:minor, release:major만입니다. hotfix tag/label은 사용하지 않습니다.
  • image tag는 zeroone-frontend:vMAJOR.MINOR.PATCH-shortCommit, zeroone-backend:vMAJOR.MINOR.PATCH-shortCommit 형식만 허용합니다.
  • prod/latest-prod는 배포 포인터일 뿐 rollback 기준으로 쓰면 validation 실패합니다.
  • backend dispatch는 metadata.backend_deploy_id 중복을 거부하고, payload/current state가 불명확하면 release record를 쓰지 않고 실패합니다.

Verification

  • Local: workflow YAML parse, actionlint, node --check, release-record validation, backend dispatch smoke, duplicate deploy-id smoke, empty previous-release fail smoke, git diff --check
  • Local: yarn typecheck
  • Local: yarn lint passed with existing warnings only
  • Push hook: next build passed (sitemap local backend 127.0.0.1:8080 warning only)
  • GitHub Actions: CI / Release Record Check / Chromatic passed on latest push

Co-authored-by: OmX <omx@oh-my-codex.dev>
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
study-platform-client-dev Ready Ready Preview, Comment May 17, 2026 0:12am

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Hyeonjun0527 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f280cb5a-9ed5-4910-885c-7c6f0496fec2

📥 Commits

Reviewing files that changed from the base of the PR and between fde1f69 and ff6672b.

📒 Files selected for processing (12)
  • .github/workflows/deploy-prod.yml
  • .github/workflows/record-backend-prod-release.yml
  • ops/agent-skills/zeroone-version-management.md
  • ops/backend-release-dispatch.md
  • ops/deploy-checklist.md
  • ops/release-intent.md
  • ops/release-record-shared-contract.md
  • ops/version-management.md
  • scripts/release/generate-backend-prod-release-record.mjs
  • scripts/release/generate-prod-release-record.mjs
  • scripts/release/resolve-prod-release-intent.mjs
  • scripts/release/validate-release-record.mjs
📝 Walkthrough

Walkthrough

프로덕션 배포 자동화를 위한 완전한 버전 관리 및 릴리즈 운영 체계를 구축합니다. 릴리즈 의도 해석, 레코드 생성/검증, 배포 워크플로, 백엔드 기록, 롤백 절차를 포함하는 자동화 파이프라인과 함께 운영 규칙, 에이전트 스킬, 체크리스트를 정의합니다.

Changes

프로덕션 릴리즈 자동화 워크플로

Layer / File(s) Summary
릴리즈 의도 해석 및 버전 결정
scripts/release/resolve-prod-release-intent.mjs
PR 라벨(`release:patch
릴리즈 레코드 검증
scripts/release/validate-release-record.mjs
생성된 YAML 릴리즈 레코드의 필수 필드(release_id, env, components, database, rollback), 이미지 태그 정규식, 포인터 태그 금지, 날짜 포함 금지, 메타데이터 일관성을 검증합니다. metadata.backend_deploy_id 중복 여부도 확인합니다.
프론트엔드 릴리즈 레코드 생성
scripts/release/generate-prod-release-record.mjs
프로덕션 배포 시 release_id, 프론트엔드/백엔드 이미지·커밋·버전, 배포 순서, 마이그레이션/롤백 정보를 조합하여 releases/prod-YYYYMMDD-HHmm.yaml 파일을 생성합니다.
백엔드 릴리즈 레코드 생성
scripts/release/generate-backend-prod-release-record.mjs
백엔드 repository_dispatch 페이로드에서 백엔드 배포 메타를 읽고, 현재 프론트엔드 상태를 추출한 뒤, 통합 YAML 릴리즈 레코드를 생성합니다.
프로덕션 배포 워크플로우
.github/workflows/deploy-prod.yml
main push 또는 수동 트리거(workflow_dispatch)에서 release intent를 해석하고, DockerHub에 이미지를 빌드/푸시하며, 원격에서 백엔드 상태를 검사한 뒤 docker-compose.prod.yml 재구성, 이미지 pull, 컨테이너 재시작, smoke/E2E 테스트, 릴리즈 레코드 생성/검증/커밋/푸시를 수행합니다. 배포 전 paths-ignore: ['releases/**', 'ops/**'] 설정으로 문서/레코드 변경은 제외합니다.
백엔드 릴리즈 기록 워크플로우
.github/workflows/record-backend-prod-release.yml
repository_dispatch backend-prod-deployed 이벤트 수신 시 백엔드 레코드를 생성하고 검증한 뒤 main에 커밋/푸시합니다. 동시성 제어(prod-release-record 그룹, 진행 중 작업 유지)를 적용합니다.
PR CI에서 릴리즈 레코드 검증
.github/workflows/release-record-check.yml
pull_request에서 releases/, scripts/release/, ops/ 변경 시 또는 수동 실행 시 모든 릴리즈 레코드를 validate-release-record.mjs로 검증합니다.

운영 규칙 및 설명서

Layer / File(s) Summary
버전 관리 소스 오브 트루스
ops/version-management.md
프론트엔드 레포지토리 기준의 ZERO-ONE 릴리즈 기록 규칙을 정의합니다. 릴리즈 ID 포맷(prod-YYYYMMDD-HHmm), 이미지 태그 정책(불변 태그만, prod/latest-prod는 포인터만), 배포 순서(DB 마이그레이션 → 백엔드 → 헬스체크 → 프론트엔드 → E2E), 릴리즈 레코드 스키마, 라벨 정책(`release:patch
릴리즈 의도 및 디스패치 계약
ops/release-intent.md, ops/backend-release-dispatch.md
PR 라벨에서 릴리즈 의도를 추출하는 규칙과 백엔드의 repository_dispatch 페이로드 스키마(release_id, backend.changed, database.changed, metadata.backend_deploy_id, 이미지/커밋/버전)를 정의합니다.
배포 체크리스트 및 롤백 절차
ops/deploy-checklist.md, ops/rollback.md
배포 전/후 확인 항목(CI 상태, 라벨 단일성, 부트스트랩 필드, 백엔드 이미지 일치 등)과 롤백 기준(고정 이미지 태그, releases/ 기록, 파괴적 DB 롤백 금지)을 명시합니다.
에이전트 스킬 및 규칙 래퍼
.claude/rules/version-management-frontend.md, .claude/skills/zeroone-version-management/SKILL.md, .codex/skills/zeroone-version-management/SKILL.md, ops/agent-skills/zeroone-version-management.md
에이전트가 버전 관리 규칙을 즉시 읽고 따르도록 지시합니다. 필수 읽기 순서, 비협상 규칙(라벨 단일성, 포인터 태그 금지, 부트스트랩, 이미지 날짜 포함), 구현 워크플로, 휴먼 핸드오프 리포트 템플릿을 정의합니다.
핵심 가이드 및 설정
AGENTS.md, CLAUDE.md, package.json
AGENTS.md의 "Production Version Management" 섹션과 CLAUDE.md Core Rules에 버전 관리 규칙 참조를 추가하고, package.jsonrelease:validate 스크립트를 등록합니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🐰 릴리즈 자동화 마법 가득,
releases/에 기록 쌓여가고,
백엔드·프론트 손맞춰 춤춘다,
워크플로우가 배포를 품고,
롤백도 준비되니 안심하게! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'O운영 릴리즈 버전관리 자동화' directly relates to the main objective of this PR: implementing automated version management for production releases.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/prod-version-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-authored-by: OmX <omx@oh-my-codex.dev>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
ops/version-management.md (1)

121-130: ⚡ Quick win

라벨 전용 규칙과 PR body fallback 규칙을 이 문서에 함께 명시해 계약을 단일화해 주세요.

현재 문구만 보면 운영 의도가 “라벨만 허용”으로 해석될 수 있는데, ops/release-intent.md는 라벨 미가용 시 body fallback을 허용합니다. 두 문서를 동시에 보는 사람에게 계약이 갈릴 수 있어요.

제안 diff
 ## Main-branch release intent
 
 Production versioning is derived from PR intent, not from per-release environment variables.
 
 Allowed release intents are exactly:
@@
 - `release:major`
 
 `hotfix` labels and `-hotfix.N` image tags are not used.
+
+If labels are unavailable in a specific workflow context, the PR body `release: patch|minor|major`
+fallback defined in `ops/release-intent.md` may be used. If both are present and disagree, fail.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/version-management.md` around lines 121 - 130, 문서 'Main-branch release
intent' 섹션(현재 ops/version-management.md)에 라벨 우선 규칙과 라벨 미사용 시 PR body fallback
규칙을 명확히 통합해 단일 계약으로 서술하세요: 허용된 intent 목록(`release:patch`, `release:minor`,
`release:major`)을 유지하되, 우선순위 규칙(라벨가 있으면 라벨을 사용하고, 라벨이 없을 경우
ops/release-intent.md에 정의된 PR body 값을 fallback으로 사용)을 명시하고 예외/예시 한두 줄을 추가해 모순이
없도록 정리합니다.
ops/deploy-checklist.md (1)

8-8: ⚡ Quick win

릴리즈 인텐트 표기 값을 명시적으로 고정해 주세요.

Line 8의 release: ... 표기는 허용 값이 열려 있어 운영자가 patch|minor|major 외 값을 넣을 여지를 남깁니다. 체크리스트에도 허용 문자열을 정확히 고정하는 편이 안전합니다.

✍️ 제안 수정안
-- Confirm the PR has exactly one release intent: `release:patch`, `release:minor`, `release:major`, or a `release: ...` line in the PR body.
+- Confirm the PR has exactly one release intent: `release:patch`, `release:minor`, `release:major`, or exactly one PR body line of `release:patch|minor|major`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/deploy-checklist.md` at line 8, Change the ambiguous "release: ..."
checklist item to enumerate the exact allowed values and require exactly one of
them; replace the text containing `release: ...` with a fixed list like
"`release:patch`, `release:minor`, or `release:major`" and update the sentence
that currently reads "Confirm the PR has exactly one release intent:
`release:patch`, `release:minor`, `release:major`, or a `release: ...` line in
the PR body." so it only accepts the three explicit tokens (`release:patch`,
`release:minor`, `release:major`) and enforces exactly one occurrence.
scripts/release/validate-release-record.mjs (4)

61-67: 💤 Low value

파일명 검증에서 basename 사용을 권장합니다.

현재 file.endsWith(expectedName) 검증은 작동하지만, path.basename(file) === expectedName 방식이 의도를 더 명확하게 표현하며, 다른 경로에 동일 이름 파일이 있을 때 발생할 수 있는 오탐을 방지합니다.

현재 Line 145의 필터링(/prod-\d{8}-\d{4}\.yaml$/)이 이미 파일명을 제한하므로 큰 문제는 아니지만, 더 명시적인 검증이 가능합니다.

♻️ basename 사용 개선안
+import { basename, join } from 'node:path';
+
 const validateFile = (file) => {
   const content = readFileSync(file, 'utf8');
   const releaseId = parseScalar(content, 'release_id');
   const expectedName = `${releaseId}.yaml`;
 
   if (!RELEASE_ID.test(releaseId))
     fail(file, `invalid release_id: ${releaseId}`);
-  if (!file.endsWith(expectedName)) {
+  if (basename(file) !== expectedName) {
     fail(file, `filename must match release_id (${expectedName})`);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/release/validate-release-record.mjs` around lines 61 - 67, The
filename check using file.endsWith(expectedName) can produce false positives for
paths; change it to compare the basename explicitly by using path.basename(file)
=== expectedName. Locate the block that computes expectedName and validates
RELEASE_ID.test(releaseId) and replace the endsWith check with a basename
equality check (use the existing expectedName variable and the file variable),
keeping the same fail(file, ...) invocation when the basename does not match.

143-160: 💤 Low value

각 파일을 두 번 읽는 것을 개선할 수 있습니다.

Line 148의 validateFile과 Line 149의 readFileSync가 동일한 파일을 중복으로 읽습니다. 파일 수가 적다면 성능상 큰 문제는 아니지만, validateFile이 검증 결과와 함께 파싱된 데이터(또는 content)를 반환하도록 리팩토링하면 파일 I/O를 줄일 수 있습니다.

현재 구조는 명확하고 작동하므로 선택적 개선 사항입니다.

♻️ 이중 읽기 제거 개선안
-const validateFile = (file) => {
+const validateFile = (file, content) => {
-  const content = readFileSync(file, 'utf8');
   const releaseId = parseScalar(content, 'release_id');
   // ... rest of validation
+  return content;
 };
 
 const files = targets
   .flatMap(listYamlFiles)
   .filter((file) => /prod-\d{8}-\d{4}\.yaml$/.test(file));
 const backendDeployIds = new Map();
 for (const file of files) {
-  validateFile(file);
-  const content = readFileSync(file, 'utf8');
+  const content = readFileSync(file, 'utf8');
+  validateFile(file, content);
   const backendDeployId = parseScalar(content, 'metadata.backend_deploy_id');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/release/validate-release-record.mjs` around lines 143 - 160, The code
currently reads each file twice via validateFile(file) and then
readFileSync(file) before parseScalar; refactor validateFile to return the file
content or parsed data so you can reuse it instead of calling readFileSync
again: modify validateFile to return the validated content (or the parsed
YAML/metadata), then replace the readFileSync + parseScalar call in the loop to
use the returned content (or parsed object) when extracting
metadata.backend_deploy_id; keep existing logic around backendDeployIds,
parseScalar usage (or adapt to the returned parsed object), and preserve the
fail(...) behavior when duplicates are found.

17-23: ⚡ Quick win

listYamlFiles가 재귀 탐색을 지원하지 않습니다.

현재 함수는 디렉터리의 직접 자식 파일만 수집하며, 중첩된 하위 디렉터리는 처리하지 않습니다. 반면 워크플로우(.github/workflows/release-record-check.yml:6)는 releases/**/*.yaml 패턴으로 중첩 디렉터리 변경도 감지합니다.

릴리즈 레코드가 releases/ 직속에만 저장된다면 현재 구현으로 충분하지만, 워크플로우 경로 패턴과 스크립트 처리 능력 간 불일치가 있습니다.

♻️ 재귀 탐색을 추가하는 개선안
 const listYamlFiles = (target) => {
   if (!existsSync(target)) return [];
   if (statSync(target).isFile()) return [target];
-  return readdirSync(target)
-    .filter((file) => file.endsWith('.yaml'))
-    .map((file) => join(target, file));
+  const entries = readdirSync(target, { withFileTypes: true });
+  return entries.flatMap((entry) => {
+    const fullPath = join(target, entry.name);
+    if (entry.isDirectory()) return listYamlFiles(fullPath);
+    return entry.name.endsWith('.yaml') ? [fullPath] : [];
+  });
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/release/validate-release-record.mjs` around lines 17 - 23, The
listYamlFiles function currently only collects .yaml files at the top-level of
the target directory; update listYamlFiles to perform a recursive directory walk
so it collects .yaml files from nested subdirectories as well (while preserving
the existing behavior when target is a file or missing). Implement this by
iterating directory entries (e.g., readdirSync with Dirent or using statSync on
each entry) and when an entry is a directory recursively call listYamlFiles (or
push results from a helper walk) and when an entry is a file with .yaml
extension add its joined path; keep using existsSync/statSync to short-circuit
non-existent or file targets and ensure returned paths are full/joined using
join().

25-42: ⚡ Quick win

정규식 기반 YAML 파서의 한계를 고려하세요.

현재 parseScalar는 단순한 key: value 구조만 파싱하며, 멀티라인 값, 배열, 주석, YAML 특수 문자 등을 처리하지 못합니다. 릴리즈 레코드가 단순한 스칼라 값만 사용한다면 작동하지만, 향후 확장성과 견고성을 위해 js-yaml 같은 검증된 라이브러리 사용을 권장합니다.

현재 구조가 단순하고 추가 의존성을 피하려는 의도라면 현재 구현도 충분합니다.

♻️ js-yaml 사용 예시
+import yaml from 'js-yaml';
 
-const parseScalar = (content, path) => {
-  const lines = content.split(/\r?\n/);
-  const stack = [];
-  for (const raw of lines) {
-    const match = raw.match(/^(\s*)([A-Za-z0-9_]+):\s*(.*)$/);
-    if (!match) continue;
-    const indent = match[1].length;
-    const key = match[2];
-    const value = match[3].trim();
-    while (stack.length && stack[stack.length - 1].indent >= indent)
-      stack.pop();
-    stack.push({ indent, key });
-    if (stack.map((item) => item.key).join('.') === path) {
-      return value.replace(/^['"]|['"]$/g, '');
-    }
-  }
-  return '';
+const parseScalar = (content, path) => {
+  const doc = yaml.load(content);
+  const keys = path.split('.');
+  let value = doc;
+  for (const key of keys) {
+    value = value?.[key];
+  }
+  return value ? String(value) : '';
 };

그리고 package.json에 의존성 추가:

"dependencies": {
  "js-yaml": "^4.1.0"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/release/validate-release-record.mjs` around lines 25 - 42, The
current parseScalar function uses a fragile regex-based YAML parser that fails
for multiline values, arrays, comments and other YAML features; replace its body
to parse the content with a proper YAML library (e.g., install and import
js-yaml and use YAML.load or safeLoad), then traverse the resulting object by
splitting the dot-path and returning the scalar (or empty string if missing).
Update parseScalar to call js-yaml's loader, handle parse errors by returning ''
or rethrowing with context, and ensure the symbol parseScalar remains the entry
point for callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/record-backend-prod-release.yml:
- Around line 24-28: 현재 Checkout step ("Checkout code" using
actions/checkout@v4) can pull the default branch for the event; explicitly pin
the branch to main by adding with: ref: main (keeping fetch-depth: 0) so the
workflow always checks out the main branch for releases; update the checkout
step in the workflow to include ref: main to enforce main-branch deployments per
the ZERO-ONE rule.

In `@ops/backend-release-dispatch.md`:
- Around line 7-13: The example trigger payload for event_type
"backend-prod-deployed" currently shows an empty client_payload which violates
the document's Required payload/fields rules; update the example JSON so
client_payload includes the actual required fields (e.g., service, version,
environment, deploy_id or whatever the "Required payload/fields" section
mandates) and show sample values, ensuring consistency with the document's
Required payload/fields schema; apply the same correction to the other example
occurrences referenced in the document.

In `@ops/rollback.md`:
- Line 27: 현재 문서의 "follow-up release/incident note" 요구만으로는 롤백 기록이 releases 디렉터리에
남지 않을 수 있으므로, ops/rollback.md의 해당 문구(“follow-up release/incident note”)를 수정해 롤백
결과를 반드시 releases/prod-*.yaml 패턴으로 기록하도록 명시하세요; 즉 문장에 최소 요구사항으로 "record rollback
outcome as a releases/prod-*.yaml release note"를 추가하고 예시 파일명 형식과 필수 필드(예:
timestamp, operator, reason, reverted_commit)도 간단히 기술하여 자동화가 SSOT로 참조할 수 있게 하세요.

---

Nitpick comments:
In `@ops/deploy-checklist.md`:
- Line 8: Change the ambiguous "release: ..." checklist item to enumerate the
exact allowed values and require exactly one of them; replace the text
containing `release: ...` with a fixed list like "`release:patch`,
`release:minor`, or `release:major`" and update the sentence that currently
reads "Confirm the PR has exactly one release intent: `release:patch`,
`release:minor`, `release:major`, or a `release: ...` line in the PR body." so
it only accepts the three explicit tokens (`release:patch`, `release:minor`,
`release:major`) and enforces exactly one occurrence.

In `@ops/version-management.md`:
- Around line 121-130: 문서 'Main-branch release intent' 섹션(현재
ops/version-management.md)에 라벨 우선 규칙과 라벨 미사용 시 PR body fallback 규칙을 명확히 통합해 단일
계약으로 서술하세요: 허용된 intent 목록(`release:patch`, `release:minor`, `release:major`)을
유지하되, 우선순위 규칙(라벨가 있으면 라벨을 사용하고, 라벨이 없을 경우 ops/release-intent.md에 정의된 PR body 값을
fallback으로 사용)을 명시하고 예외/예시 한두 줄을 추가해 모순이 없도록 정리합니다.

In `@scripts/release/validate-release-record.mjs`:
- Around line 61-67: The filename check using file.endsWith(expectedName) can
produce false positives for paths; change it to compare the basename explicitly
by using path.basename(file) === expectedName. Locate the block that computes
expectedName and validates RELEASE_ID.test(releaseId) and replace the endsWith
check with a basename equality check (use the existing expectedName variable and
the file variable), keeping the same fail(file, ...) invocation when the
basename does not match.
- Around line 143-160: The code currently reads each file twice via
validateFile(file) and then readFileSync(file) before parseScalar; refactor
validateFile to return the file content or parsed data so you can reuse it
instead of calling readFileSync again: modify validateFile to return the
validated content (or the parsed YAML/metadata), then replace the readFileSync +
parseScalar call in the loop to use the returned content (or parsed object) when
extracting metadata.backend_deploy_id; keep existing logic around
backendDeployIds, parseScalar usage (or adapt to the returned parsed object),
and preserve the fail(...) behavior when duplicates are found.
- Around line 17-23: The listYamlFiles function currently only collects .yaml
files at the top-level of the target directory; update listYamlFiles to perform
a recursive directory walk so it collects .yaml files from nested subdirectories
as well (while preserving the existing behavior when target is a file or
missing). Implement this by iterating directory entries (e.g., readdirSync with
Dirent or using statSync on each entry) and when an entry is a directory
recursively call listYamlFiles (or push results from a helper walk) and when an
entry is a file with .yaml extension add its joined path; keep using
existsSync/statSync to short-circuit non-existent or file targets and ensure
returned paths are full/joined using join().
- Around line 25-42: The current parseScalar function uses a fragile regex-based
YAML parser that fails for multiline values, arrays, comments and other YAML
features; replace its body to parse the content with a proper YAML library
(e.g., install and import js-yaml and use YAML.load or safeLoad), then traverse
the resulting object by splitting the dot-path and returning the scalar (or
empty string if missing). Update parseScalar to call js-yaml's loader, handle
parse errors by returning '' or rethrowing with context, and ensure the symbol
parseScalar remains the entry point for callers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac4e7ff4-f619-42b8-a45e-b6bd0026c098

📥 Commits

Reviewing files that changed from the base of the PR and between d70f7ad and fde1f69.

📒 Files selected for processing (20)
  • .claude/rules/version-management-frontend.md
  • .claude/skills/zeroone-version-management/SKILL.md
  • .codex/skills/zeroone-version-management/SKILL.md
  • .github/workflows/deploy-prod.yml
  • .github/workflows/record-backend-prod-release.yml
  • .github/workflows/release-record-check.yml
  • AGENTS.md
  • CLAUDE.md
  • ops/agent-skills/zeroone-version-management.md
  • ops/backend-release-dispatch.md
  • ops/deploy-checklist.md
  • ops/release-intent.md
  • ops/rollback.md
  • ops/version-management.md
  • package.json
  • releases/.gitkeep
  • scripts/release/generate-backend-prod-release-record.mjs
  • scripts/release/generate-prod-release-record.mjs
  • scripts/release/resolve-prod-release-intent.mjs
  • scripts/release/validate-release-record.mjs

Comment on lines +24 to +28
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

체크아웃 브랜치를 main으로 명시 고정하세요.

현재는 repository_dispatch 실행 시점의 기본 브랜치 기준으로 체크아웃될 수 있어, 커밋/푸시 대상(main)과 작업 베이스가 어긋날 위험이 있습니다.

수정 제안
       - name: Checkout code
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
+          ref: main

Based on learnings: Main-branch production deployments must follow ops/version-management.md (ZERO-ONE Version Management Rule - Frontend Repository).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/record-backend-prod-release.yml around lines 24 - 28, 현재
Checkout step ("Checkout code" using actions/checkout@v4) can pull the default
branch for the event; explicitly pin the branch to main by adding with: ref:
main (keeping fetch-depth: 0) so the workflow always checks out the main branch
for releases; update the checkout step in the workflow to include ref: main to
enforce main-branch deployments per the ZERO-ONE rule.

Comment on lines +7 to +13
Backend production deploy success must call the frontend repository with `repository_dispatch` or an equivalent API trigger.

```json
{
"event_type": "backend-prod-deployed",
"client_payload": {}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

트리거 예시의 client_payload가 필수 스키마와 충돌합니다.

Line 9-13 예시를 그대로 따르면 빈 payload가 전송되어, 같은 문서의 Required payload/fields 규칙을 즉시 위반합니다. 트리거 예시는 최소한 “필수 필드를 포함한 payload” 형태로 맞춰야 계약 오해를 막을 수 있습니다.

제안 diff
 {
   "event_type": "backend-prod-deployed",
-  "client_payload": {}
+  "client_payload": {
+    "release_id": "prod-YYYYMMDD-HHmm",
+    "env": "prod",
+    "backend": {
+      "image": "zeroone-backend:vMAJOR.MINOR.PATCH-shortCommit",
+      "commit": "shortCommit",
+      "version": "vMAJOR.MINOR.PATCH",
+      "changed": true
+    },
+    "database": {
+      "changed": false,
+      "migration_version": "none",
+      "migration_files": []
+    },
+    "rollback": {
+      "backend": "zeroone-backend:vMAJOR.MINOR.PATCH-shortCommit"
+    },
+    "metadata": {
+      "release_intent": "patch",
+      "bootstrap_mode": false,
+      "backend_deploy_id": "backend-prod-unique-id"
+    }
+  }
 }

Also applies to: 22-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/backend-release-dispatch.md` around lines 7 - 13, The example trigger
payload for event_type "backend-prod-deployed" currently shows an empty
client_payload which violates the document's Required payload/fields rules;
update the example JSON so client_payload includes the actual required fields
(e.g., service, version, environment, deploy_id or whatever the "Required
payload/fields" section mandates) and show sample values, ensuring consistency
with the document's Required payload/fields schema; apply the same correction to
the other example occurrences referenced in the document.

Comment thread ops/rollback.md
3. Deploy the fixed frontend/backend images.
4. Run backend health check.
5. Run frontend smoke/E2E check.
6. Record the rollback outcome in a follow-up release/incident note.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

롤백 결과 기록 위치를 releases/로 강제해 주세요.

Line 27이 “follow-up release/incident note”만 요구하면, 실제 롤백 이력이 releases/에 남지 않아 다음 배포/롤백 자동화가 참조하는 SSOT가 깨질 수 있습니다. 최소 요구사항에 releases/prod-*.yaml 기록을 명시적으로 포함해야 합니다.

🧭 제안 수정안
-6. Record the rollback outcome in a follow-up release/incident note.
+6. Record the rollback as a new `releases/prod-YYYYMMDD-HHmm.yaml` entry (with rollback context), and optionally mirror details in an incident note.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
6. Record the rollback outcome in a follow-up release/incident note.
6. Record the rollback as a new `releases/prod-YYYYMMDD-HHmm.yaml` entry (with rollback context), and optionally mirror details in an incident note.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/rollback.md` at line 27, 현재 문서의 "follow-up release/incident note" 요구만으로는
롤백 기록이 releases 디렉터리에 남지 않을 수 있으므로, ops/rollback.md의 해당 문구(“follow-up
release/incident note”)를 수정해 롤백 결과를 반드시 releases/prod-*.yaml 패턴으로 기록하도록 명시하세요; 즉
문장에 최소 요구사항으로 "record rollback outcome as a releases/prod-*.yaml release note"를
추가하고 예시 파일명 형식과 필수 필드(예: timestamp, operator, reason, reverted_commit)도 간단히 기술하여
자동화가 SSOT로 참조할 수 있게 하세요.

Co-authored-by: OmX <omx@oh-my-codex.dev>
@Hyeonjun0527 Hyeonjun0527 merged commit 8aef3ae into develop May 17, 2026
11 of 12 checks passed
@Hyeonjun0527 Hyeonjun0527 deleted the chore/prod-version-management branch May 17, 2026 11:46
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