Repository navigation
[CI] 전국 후보 갱신을 CI 자동 PR로 만들고 후보 병합 시 release-candidate를 자동 실행 - #928
Conversation
- nationwide-candidate-refresh.yml(workflow_dispatch, main 전용): 입력(releaseSequence·요청/승인 역할)을 plan-datapack-release-chain로 먼저 검증하고(커밋된 후보보다 큰 sequence, 서로 다른 역할, 안전한 토큰), 실행 시각을 후보 시계로 refresh-nationwide-candidate를 돌린다. NATIONWIDE_CANDIDATE_REFRESH_OUTPUTS 밖의 경로가 바뀌거나 변화가 없으면 실패한다. 결과는 automation/927-… 브랜치의 draft PR로 올리고, GITHUB_TOKEN PR에는 pull_request CI가 붙지 않으므로 그 head에 ci.yml을 dispatch한다. - datapack-release-candidate-chain.yml(push to main, 후보 spec·release request 경로): 저장소 파일로 modeArgs를 만들어 datapack-release.yml을 mode=release-candidate로 dispatch한다. hub promotion이 workflow_dispatch 후보 run만 받기 때문이다. release request가 spec에 결속되지 않거나 RC 증거 파일이 없으면 dispatch 전에 실패한다. 후보가 main에서 다시 바뀌었으면 새 push 실행에 맡긴다. - RED: 새 테스트가 도구·workflow 부재로 실패한 뒤 구현. Refs #927 Refs #870
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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. Comment |
AquilaXk
left a comment
There was a problem hiding this comment.
Actionable comments posted: 4
🎯 Linked issue & acceptance criteria audit
- Linked issue: #927 CI: nationwide candidate refresh as an automation PR and automatic RC
- Goal summary: Run the candidate refresh in CI as an automation PR and dispatch the RC on merge.
- Acceptance criteria verified: 5/5
| ID | Acceptance criterion | Status | Evidence |
|---|---|---|---|
| AC-DISPATCH | A main-only dispatch workflow takes the sequence and both roles | PASS | .github/workflows/nationwide-candidate-refresh.yml |
| AC-INPUTS | Inputs are validated before the candidate command runs | PASS | tools/ci/plan-datapack-release-chain.mjs |
| AC-OUTPUTS | Only the declared output paths may change and no change fails | PASS | .github/workflows/nationwide-candidate-refresh.yml |
| AC-PR-CI | The result goes to an automation PR whose head receives a ci.yml dispatch | PASS | .github/workflows/nationwide-candidate-refresh.yml |
| AC-RC | A candidate landing on main dispatches the RC with bound modeArgs | PASS | .github/workflows/datapack-release-candidate-chain.yml |
🛡️ Adversarial audit evidence
- Falsifiability verified: Yes (Probes on planner lines 31, 39, 45, 65 and 86 were killed by assertion failures.)
- Hollow assertions detected: 0
- Production backdoors detected: 0
- Ground-truth sources verified:
datapack-release.yml workflow_dispatch input schemahub datapack-promotion.yml event checkIssue 927 body acceptance criteria
- Mutation testing evidence:
probe lines 31, 39, 45, 65, 86 all killed
🤖 Prompt for all review comments with AI agents
Could we verify each finding against the current code and keep only those that
still apply? Could we make the smallest validated fix and briefly note why any
finding no longer applies?
Inline comments:
- `@.github/workflows/datapack-release-candidate-chain.yml:41-46`
Could the step capture the exit code and treat only exit 1 as superseded, failing
the job on any other code? A shell-level test with a bad ref would pin the failure.
- `@.github/workflows/nationwide-candidate-refresh.yml:75-82`
Would a failure cleanup step that deletes the pushed branch when PR creation fails,
or a final failure report naming the branch, keep the repository free of orphan
branches?
- `@tools/ci/plan-datapack-release-chain.test.mjs:191-200`
Could the expected key set and the fixed argument values from the manual run be
pinned as literals, with the approvalId pattern asserted instead of echoed?
- `@tools/ci/plan-datapack-release-chain.test.mjs:202-240`
Would a small test that runs the guard commands in a temporary git repo, with an
extra tracked change and with no change, give the guard real coverage?
---
Outside diff comments:
None.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Aquila review of easysubway-data PR 928 using CLAUDE.md and the issue 927 body
Review profile: ASSERTIVE
Plan: Aquila fallback
Run ID: N/A (Aquila fallback)
Review source: Aquila CLI
Repository visibility: public GitHub repository
Trigger:
Manual discovery review requested by QA for the draft PR at current head
Base:
main@53f01fc5c4c5d2b7630c2c6dcdb79608200eaa1c
Head:
ci/nationwide-candidate-refresh-chain-927@3e028feed947a421870edd28796f26b14a6b9248
Command:
node aquila-review/scripts/post-review.mjs input.json payload-1.json --repo AquilaXk/easysubway-data --pr 928
📥 Commits
Reviewing files changed in this pull request between:
- Base:
53f01fc5c4c5d2b7630c2c6dcdb79608200eaa1c - Head:
3e028feed947a421870edd28796f26b14a6b9248
📒 Files selected for processing (4)
.github/workflows/datapack-release-candidate-chain.yml.github/workflows/nationwide-candidate-refresh.ymltools/ci/plan-datapack-release-chain.mjstools/ci/plan-datapack-release-chain.test.mjs
📜 Review details
#928 리뷰 F1: if git diff --quiet ...; else 분기는 차이(종료 1)와 오류(잘못된 ref 등 128)를 모두 "superseded"로 처리해, 오류가 나도 run이 녹색으로 끝나고 RC가 dispatch되지 않았다. 종료 코드 0만 current, 1만 superseded로 두고 그 밖의 코드는 "candidate supersede check failed"로 실패한다. RED: run 블록을 임시 git 저장소(bare origin 대신 로컬 origin)에서 실제 bash로 실행하는 테스트가 없는 sha에서 종료 0으로 실패. Refs #927
#928 리뷰 F2: 브랜치 push 뒤 PR 생성이나 ci.yml dispatch가 실패하면 run id가 붙은 브랜치와 CI 없는 PR이 남았다. - PR URL을 CANDIDATE_PR_URL로 남긴다. - 마지막 단계 "Remove the candidate refresh branch after a later failure"(failure() && 브랜치가 있을 때)가 PR이 있으면 gh pr close --delete-branch로 닫고, 없으면 git push origin --delete로 브랜치를 지운다. job은 실패로 남으므로 CI dispatch 실패도 run 실패로 드러난다. RED: 정리 단계 부재로 새 테스트 실패. 테스트는 정리 run 블록을 임시 git 저장소와 가짜 gh로 실제 실행해 두 경우의 브랜치 삭제를 확인한다. Refs #927
#928 리뷰 F3: 커밋된 파일 테스트가 releaseRequestId를 같은 파일의 approvalId와 비교해 순환 검증이었다. - run 37109648483 "Parse modeArgs" 로그의 MODE_ARGS_INPUT 원문을 상수로 두고, seq126 후보 식별자로 만든 modeArgs가 바이트 그대로 같은지 단언한다. - 커밋된 파일 테스트는 releaseRequestId를 패턴(release-request-nationwide-candidate-YYYYMMDD-seqN)으로만 보고, 나머지 고정 인자는 같은 원문 상수와 비교한다(이후 후보 PR이 release-request를 바꿔도 의미가 유지된다). RED: allowGaps를 "true"로 바꾼 변이에서 새 테스트 2건이 실패(기존 키 순서 테스트는 이 변이를 잡지 못했다). Refs #927
#928 리뷰 F4: 출력 범위·무변경 가드가 문자열 일치로만 테스트됐다. - "Verify candidate refresh output scope" 단계로 분리한다(커밋·push 단계와 분리). bash 3.2에서도 도는 문법(mapfile 제거)으로 쓴다. - 출력 목록을 못 읽으면 실패한다(명령 치환 실패가 set -e로 드러남). - git diff --cached --quiet는 0(무변경)·1(변경)만 정상 분기로 두고 그 밖의 종료 코드는 실패한다(기존 && 형태는 오류를 변경으로 오인했다). - 테스트는 이 run 블록을 임시 git 저장소에서 실행한다: 선언 출력만 변경(통과·스테이징 확인), 다른 추적 파일 변경, 미추적 파일, 무변경, 출력 목록 모듈 부재. RED: 분리된 가드 단계 부재로 새 테스트 실패. Refs #927
|
No description provided. |
|
No description provided. |
|
#928이 squash로 main에 병합되며 이 PR의 base가 main으로 바뀌고 DIRTY가 됐다. 이력을 다시 쓰지 않도록 rebase 대신 main을 합쳤다. - #928 파일 4개(두 workflow, plan 도구·테스트): main 쪽 내용이 #928 마지막 커밋(9c6aeb7)과 같음을 확인하고, 그 위에 D3 변경을 더한 이 브랜치 내용을 유지했다. - data-test-ownership.json: main의 항목에 scheduled-release-authority 테스트 한 줄을 더했다. - documentation-fragment.json: main 기준으로 다시 생성했다(datapack-release.yml blob 갱신). Refs #929
* [CI] 전국 후보 갱신을 CI 자동 PR로 만들고 후보 병합 시 release-candidate를 자동 실행 - nationwide-candidate-refresh.yml(workflow_dispatch, main 전용): 입력(releaseSequence·요청/승인 역할)을 plan-datapack-release-chain로 먼저 검증하고(커밋된 후보보다 큰 sequence, 서로 다른 역할, 안전한 토큰), 실행 시각을 후보 시계로 refresh-nationwide-candidate를 돌린다. NATIONWIDE_CANDIDATE_REFRESH_OUTPUTS 밖의 경로가 바뀌거나 변화가 없으면 실패한다. 결과는 automation/927-… 브랜치의 draft PR로 올리고, GITHUB_TOKEN PR에는 pull_request CI가 붙지 않으므로 그 head에 ci.yml을 dispatch한다. - datapack-release-candidate-chain.yml(push to main, 후보 spec·release request 경로): 저장소 파일로 modeArgs를 만들어 datapack-release.yml을 mode=release-candidate로 dispatch한다. hub promotion이 workflow_dispatch 후보 run만 받기 때문이다. release request가 spec에 결속되지 않거나 RC 증거 파일이 없으면 dispatch 전에 실패한다. 후보가 main에서 다시 바뀌었으면 새 push 실행에 맡긴다. - RED: 새 테스트가 도구·workflow 부재로 실패한 뒤 구현. Refs #927 Refs #870 * [Fix] RC chain의 후보 변경 판정에서 git diff 오류를 건너뛰기로 처리하지 않고 실패 #928 리뷰 F1: if git diff --quiet ...; else 분기는 차이(종료 1)와 오류(잘못된 ref 등 128)를 모두 "superseded"로 처리해, 오류가 나도 run이 녹색으로 끝나고 RC가 dispatch되지 않았다. 종료 코드 0만 current, 1만 superseded로 두고 그 밖의 코드는 "candidate supersede check failed"로 실패한다. RED: run 블록을 임시 git 저장소(bare origin 대신 로컬 origin)에서 실제 bash로 실행하는 테스트가 없는 sha에서 종료 0으로 실패. Refs #927 * [Fix] 후보 갱신 push 뒤 단계가 실패하면 PR을 닫고 automation 브랜치를 지우기 #928 리뷰 F2: 브랜치 push 뒤 PR 생성이나 ci.yml dispatch가 실패하면 run id가 붙은 브랜치와 CI 없는 PR이 남았다. - PR URL을 CANDIDATE_PR_URL로 남긴다. - 마지막 단계 "Remove the candidate refresh branch after a later failure"(failure() && 브랜치가 있을 때)가 PR이 있으면 gh pr close --delete-branch로 닫고, 없으면 git push origin --delete로 브랜치를 지운다. job은 실패로 남으므로 CI dispatch 실패도 run 실패로 드러난다. RED: 정리 단계 부재로 새 테스트 실패. 테스트는 정리 run 블록을 임시 git 저장소와 가짜 gh로 실제 실행해 두 경우의 브랜치 삭제를 확인한다. Refs #927 * [Test] RC modeArgs를 수동 RC run 37109648483의 실제 입력 원문으로 고정 #928 리뷰 F3: 커밋된 파일 테스트가 releaseRequestId를 같은 파일의 approvalId와 비교해 순환 검증이었다. - run 37109648483 "Parse modeArgs" 로그의 MODE_ARGS_INPUT 원문을 상수로 두고, seq126 후보 식별자로 만든 modeArgs가 바이트 그대로 같은지 단언한다. - 커밋된 파일 테스트는 releaseRequestId를 패턴(release-request-nationwide-candidate-YYYYMMDD-seqN)으로만 보고, 나머지 고정 인자는 같은 원문 상수와 비교한다(이후 후보 PR이 release-request를 바꿔도 의미가 유지된다). RED: allowGaps를 "true"로 바꾼 변이에서 새 테스트 2건이 실패(기존 키 순서 테스트는 이 변이를 잡지 못했다). Refs #927 * [Fix] 후보 출력 범위 가드를 별도 단계로 나누고 임시 git 저장소에서 실제 실행해 검증 #928 리뷰 F4: 출력 범위·무변경 가드가 문자열 일치로만 테스트됐다. - "Verify candidate refresh output scope" 단계로 분리한다(커밋·push 단계와 분리). bash 3.2에서도 도는 문법(mapfile 제거)으로 쓴다. - 출력 목록을 못 읽으면 실패한다(명령 치환 실패가 set -e로 드러남). - git diff --cached --quiet는 0(무변경)·1(변경)만 정상 분기로 두고 그 밖의 종료 코드는 실패한다(기존 && 형태는 오류를 변경으로 오인했다). - 테스트는 이 run 블록을 임시 git 저장소에서 실행한다: 선언 출력만 변경(통과·스테이징 확인), 다른 추적 파일 변경, 미추적 파일, 무변경, 출력 목록 모듈 부재. RED: 분리된 가드 단계 부재로 새 테스트 실패. Refs #927 * [Feat] 정기 실행 전용 2인 역할을 계약에 두고 release request에 후보 생성 run을 결속 QA 결정 D3(A)(2026-10-04): 정기 실행이 사람 승인 없이 후보를 만들 수 있게 하되, 그 사실과 근거를 계약으로 남긴다. - lib/scheduled-release-authority: 고정 역할 requestedBy datapack-scheduled-refresh·approvedBy datapack-release-gates. - 이 두 라벨은 정확한 쌍으로만, schedule·workflow_run 이벤트에서만 쓸 수 있다. 사람 역할은 workflow_dispatch(또는 로컬)에서만 쓴다. - gateRun(repository, workflowPath, runId, runAttempt, event, headSha)은 Actions 기본 환경 변수로만 만들고, main의 nationwide-candidate-refresh.yml run이 아니면 거부한다. GitHub run 기록과 대조하는 함수도 둔다. - verify-release-request-binding: 정기 역할이면 gateRun이 필수이고 그 event가 정기·체인이어야 한다. 사람 역할 request에 정기 run을 붙일 수 없다. 이 검사는 build, 후보 갱신 결속 검증, RC chain, datapack-release의 binding 단계에 모두 걸린다. - build-nationwide-candidate --gate-run, refresh-nationwide-candidate --gate-run: release request에 gateRun을 넣고 결속 검증에서 이번 run과 같은지 본다. 정기 역할인데 --gate-run이 없으면 시작하지 않는다. - plan-datapack-release-chain: candidate-refresh --event(정기·체인이면 사람 입력 없이 고정 역할과 커밋된 후보+1 sequence, 사람 dispatch는 예약 라벨 거부), gate-run 하위 명령, release-candidate-mode-args --gate-run-record(결속된 run이 main에서 성공한 그 run일 때만 modeArgs 생성). - nationwide-candidate-refresh.yml: schedule(22:23 UTC)을 추가하되 vars.DATAPACK_SCHEDULED_CANDIDATE_REFRESH == 'true'일 때만 돈다. 모든 run이 gateRun을 기록해 후보에 결속한다. - datapack-release-candidate-chain.yml: gateRun이 있으면 gh api로 run 기록을 받아 대조한 뒤에만 RC를 dispatch한다. - release-request 스키마에 선택 필드 gateRun을 추가했다. RED: lib 모듈 부재, binding·build·refresh·plan 새 테스트가 변경 전 코드에서 실패. 실측: 저장소 사본에서 정기 역할과 gate-run 파일로 후보를 갱신(후보 시계 2026-10-03T05:50:00.000Z, seq 127)하면 release request에 gateRun이 들어가고 verify-release-request-binding CLI가 PASS다. Refs #929 Refs #870 * [Fix] release request의 gateRun을 후보를 빌드한 커밋(builderGitSha)에 결속 #929 D3 보안 보강: gateRun 형식만 보면 다른 정기 run 기록을 가져다 붙인 request도 통과할 수 있다. gateRun.headSha가 build spec builderGitSha와 다르면 결속 위반으로 실패한다. 후보 갱신 workflow는 checkout한 main 커밋에서 빌드하므로 정상 경로에서는 같다. RED: 다른 커밋 headSha를 붙인 request가 위반 없이 통과해 새 단언이 실패. 실측: 저장소 사본에서 HEAD를 headSha로 둔 gate-run 파일로 정기 역할 후보 갱신이 성공하고 binding CLI가 PASS다. Refs #929 * [Refactor] SonarCloud 지적 반영: gateRun 키 비교의 정렬 제거·경로 정규식 대신 split·인자 검사 함수 분리 S2871(비교 함수 없는 sort), S8786(역추적 정규식), S3776(parseRefreshNationwideCandidateArgs 복잡도)을 동작 변경 없이 고친다. 관련 테스트 65/65 통과. Refs #929 * [Fix] 정기 전용 역할을 schedule 이벤트로만 한정 #931 리뷰 F3: 후보 갱신 workflow에는 workflow_run 트리거가 없는데 workflow_run을 정기 이벤트로 받아 허용 범위가 넓었다. SCHEDULED_ROLE_EVENTS를 ["schedule"]로 줄이고, planner도 workflow_run을 CANDIDATE_REFRESH_EVENT로 거부한다. RED: workflow_run 거부 단언 5건이 변경 전 코드에서 실패. Refs #929 * [Fix] gate run 기록 대조에 후보 시계 실행 창과 head_repository를 더해 지난 run 재사용 차단 #931 리뷰 F2: run 기록 대조가 run의 존재만 증명해, 공개된 지난 정기 성공 run의 id·커밋을 손으로 쓴 request에 붙일 수 있었다. - 후보 시계(build spec publishedAt)가 그 run의 실행 창(run_started_at..updated_at) 안이어야 한다. 후보 시계는 plan 단계의 실행 시각이라 정상 경로에서는 항상 창 안이다(QA 권장 (b)보다 좁은 ±0 창). - head_repository.full_name도 같은 저장소여야 한다(QA 권장 (c)). - 창 정보가 없거나 후보 시계 형식이 틀리면 실패한다. RED: 창 밖 후보 시계·포크 head_repository·창 정보 누락이 변경 전 코드에서 통과해 새 테스트 2건 실패. Refs #929 * [Fix] RC·production-publish 결속 단계에서 gateRun을 GitHub run 기록과 대조 #931 리뷰 F1(P1, 보안): gateRun 실재 대조가 RC chain에만 있어, 수동 RC나 production-publish는 손으로 쓴 gateRun(event schedule, 임의 runId)을 형식 검사만으로 통과시켰다. - verify-release-request-binding CLI: request에 gateRun이 있으면 --gate-run-record가 필수이고 gateRunRecordViolations(후보 시계 창 포함)를 통과해야 한다. gateRun 없는 request에 기록을 넘기면 실패한다. - datapack-release.yml "Verify release request binding": production-publish와 release request를 쓰는 RC에서 돈다. request의 gateRun.runId로 gh api 기록을 받아 CLI에 넘긴다. 문서 파편을 갱신했다. RED: 기록 없이 통과하던 정기 역할 request CLI 테스트와 binding 단계 계약 테스트가 실패. Refs #929 * [Fix] RC chain이 검증한 커밋으로만 RC를 dispatch하고 dispatch된 run의 커밋을 확인 #931 리뷰 F1(P1, 보안): --ref main으로 dispatch해, 검증 뒤 main에 들어온 다른 커밋이 RC로 빌드될 수 있었다. workflow_dispatch는 SHA를 받지 못하므로 - dispatch 직전 main 커밋이 검증한 GITHUB_SHA와 다르면 dispatch하지 않고 실패한다. - dispatch 뒤 github-actions[bot]이 만든 RC run을 찾아 head_sha가 GITHUB_SHA인지 확인하고, 다르면 그 run을 취소하고 실패한다. run을 찾지 못해도 실패한다. 성공하면 rc_run_id를 출력한다. RED: dispatch run 블록을 가짜 gh로 실행하는 테스트(커밋 일치·main 이동·다른 커밋 run)가 변경 전 workflow에서 실패. 실측: 같은 gh api 질의를 실제 저장소에 읽기로 실행해 run id·head_sha 출력 형식을 확인했다. Refs #929



Related issue
Refs #927
Refs #870
Summary
refresh-nationwide-candidate.mjs)을 지금은 에이전트가 로컬에서 실행하고 산출물을 손으로 커밋한다. [Refactor] 데이터 파이프라인 운영 방식 전환: 자동 갱신·개발/발행 게이트 분리·생성 산출물 축소 #870 결정(도구 PR과 데이터 PR 분리, 데이터 PR은 자동 생성 PR만)과 맞지 않는다.datapack-release.yml을 dispatch한다. 그런데 modeArgs는 모두 저장소 파일로 정해진다(마지막 RC run 37109648483의 modeArgs와 같다).datapack-promotion.yml은 후보 run의 event가workflow_dispatch여야 받는다. 그래서 RC를 push 이벤트로 바로 돌리면 promotion에서 거부된다.pull_requestCI가 붙지 않는다. 그래서 required check가 영영 생기지 않는다.Nationwide Candidate Refresh(dispatch 1회)가 CI에서 후보를 다시 만들고 draft PR을 연다. 그 PR head에ci.yml을 dispatch해 required check를 붙인다.Data Pack Release Candidate Chain이 RC를workflow_dispatch로 실행한다.Changes
tools/ci/plan-datapack-release-chain.mjs(신규)candidate-refresh[A-Za-z0-9][A-Za-z0-9._-]{0,63}토큰만 받는다.nationwide_routing_android_v1만 받는다.release-candidate-mode-argsreleaseRequestBindingViolations를release-request-<candidateId>기대값과 함께 그대로 쓴다. RC workflow와 같은 술어다..github/workflows/nationwide-candidate-refresh.yml(신규)workflow_dispatch전용, main 전용이다.env로만 run에 넘긴다(셸 주입 차단).automation/927-nationwide-candidate-refresh-<run>브랜치 push → draft PR →ci.ymldispatchVerify candidate refresh output scope, 리뷰 F4):NATIONWIDE_CANDIDATE_REFRESH_OUTPUTS밖의 변경이나 미추적 파일이 있으면 실패한다. 변화가 없어도 실패하고, git 오류(종료 코드 2 이상)도 실패한다. 테스트는 이 단계를 임시 git 저장소에서 실제로 실행한다..github/workflows/datapack-release-candidate-chain.yml(신규)datapack-release.yml을mode=release-candidate,targetChannel=production으로 dispatch한다.tools/ci/data-test-ownership.json: 새 테스트 등록(owner data870)Scope
Included
Excluded
Ownership / dependencies
tools/ci/data-test-ownership.json을 [Fix] 자정 이후 시발 열차 시각을 데이터팩 SQLite에 운행일 경계(03:00) 기준 24시 이후 초로 적재 #921·[Fix] 1호선 주말 원천 중복 행을 공식 근거로 격리하고 같은 출발 trip 중복을 후보 생성에서 차단 #924·#926도 고친다. 서로 다른 줄에 한 줄씩 넣었다. [Fix] 부산·대구·대전 시간표 달력에 KASI 공휴일 예외를 싣고 공휴일 달력 불변식 추가 #922·#924는 후보 산출물을 다시 만든다. 이 PR은 그 파일을 바꾸지 않지만, 그 PR들이 병합되면 이 chain이 RC를 dispatch한다.Contract & Compatibility
Version impact
Product gate impact
Provenance impact
Version decision
Verification
ERR_MODULE_NOT_FOUND, 도구 구현 뒤에는 workflow 2개 부재로 2건 실패. 구현 후node --test tools/ci/plan-datapack-release-chain.test.mjs: 11/11 통과refresh-nationwide-candidate·verify-release-request-binding테스트 포함 32/32 통과.actionlint새 workflow 2개: 오류 없음.data-test-discovery verify, anti-cheat guard, 문서 파편 검사 통과Data contracts결과는 PR check에서 확인actions: write(같은 저장소 workflow dispatch)뿐이다. dispatch 입력은 env로만 run에 넘긴다(테스트로 고정). publish·rollback 모드는 쓰지 않는다(테스트로 고정).Not run
Nationwide Candidate Refresh를 한 번 dispatch하고, 생성 PR의 CI와 병합 뒤 RC dispatch를 확인한다.Risk
admission freshness mismatch for busan-transportation-accessibility). 접근성·지역 시간표 11개 원천의 inventory admission 창(수집+1일)이 이미 지났기 때문이다.Rollout / Recovery
Data Pack Releaserun이 eventworkflow_dispatch, moderelease-candidate로 생긴다.Review focus
Checklist