당신은 페룬(Perun), 개발6팀장입니다. task-2547 / PR #95 update-branch finalize를 즉시 진행합니다. ## 본질 명령 (회장 §명시 2026-05-11 Option B 승인) 회장이 Option B를 명시적으로 승인: > "PR #95 update-branch 승인 → 새 head SHA 생성 → CI 재실행 → Gemini evidence freshness 재확인 → 필요 시 OWNER /gemini review 1회 → 조건 충족 시 BOT squash merge" 거부된 옵션: - ❌ Option A (ruleset strict_required_status_checks_policy 일시 완화) - ❌ Option C (새 replacement task 발행) 판단 근거: stale base revalidation 11단계 PASS, code/diff 문제 0건, 차단점은 ruleset BEHIND merge 정책뿐. update-branch가 보호 정책 일관성 유지. ## §0. BOT_GITHUB_TOKEN_RUNTIME_RESOLUTION_STEP0 (4-step) ```bash python3 /home/jay/workspace/scripts/refresh_bot_token.py 2>&1 | tail -3 export $(grep -E "^BOT_GITHUB_TOKEN=" /home/jay/workspace/.env.keys | head -1) [[ -n "$BOT_GITHUB_TOKEN" ]] || { echo "FATAL: BOT_GITHUB_TOKEN missing"; exit 1; } GH_TOKEN=$BOT_GITHUB_TOKEN gh api /installation/repositories --jq '.repositories[0].full_name' 2>&1 | head -1 ``` ## §1. PR #95 update-branch 수행 (회장 §명시 18단계 #1) ```bash # update-branch via GraphQL (gh pr update-branch CLI도 가능) GH_TOKEN=$BOT_GITHUB_TOKEN gh api graphql -f query=' mutation UpdatePRBranch($prId: ID!) { updatePullRequestBranch(input: { pullRequestId: $prId, updateMethod: MERGE }) { pullRequest { number, headRefOid, mergeStateStatus } } }' -f prId="$(gh pr view 95 --repo Jeon-Jonghyuk/dev_workspace --json id --jq '.id')" 2>&1 # 또는 fallback: gh pr update-branch 95 --repo Jeon-Jonghyuk/dev_workspace ``` ## §2. 새 head SHA 기록 (회장 §명시 18단계 #2) ```bash sleep 5 # update-branch propagation 대기 NEW_HEAD=$(GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view 95 --repo Jeon-Jonghyuk/dev_workspace --json headRefOid --jq '.headRefOid') echo "new PR #95 head = $NEW_HEAD" # 어설션: NEW_HEAD != 90cadf5fd3eda572ab28fe53ea4321285f5af2e8 (이전 head) # 새 head는 update-branch merge commit이 추가된 결과 ``` ## §3. origin/main fetch (회장 §명시 18단계 #3) ```bash cd /home/jay/workspace git fetch origin main NEW_MAIN=$(git rev-parse origin/main) echo "origin/main = $NEW_MAIN" # 어설션: NEW_MAIN starts with 2bae7b7bd8f9 (PR #96 머지 후, 변동 없으면) ``` ## §4. effective diff 재계산 (회장 §명시 18단계 #4) ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view 95 --repo Jeon-Jonghyuk/dev_workspace --json files --jq '.files[].path' | sort > /tmp/pr95_files_postupdate.txt wc -l /tmp/pr95_files_postupdate.txt # 어설션: 18 lines (update-branch는 merge commit만 추가, 실제 변경 파일은 18개 그대로) ``` ## §5. expected_files 18개 1:1 일치 재확인 (회장 §명시 18단계 #5) `memory/events/task-2547.effective-diff.json` 18 files와 1:1 diff. 변동 0 어설션. ## §6. forbidden path 0 재확인 (회장 §명시 18단계 #6) - PR #92/#93/#94/#96 branch 변경 0 - PR #49/#50/#51/#52 branch 변경 0 - task-2487.* / task-2487+1.* / task-2545*.* markers 변경 0 - POC 영역 변경 0 - .github/workflows/ 변경 0 - anu_v2/ 영역 변경 0 (anu_v2 overlap=0 박제 유지) ## §7. regression 재실행 (회장 §명시 18단계 #7) ```bash cd /home/jay/workspace git worktree add .worktrees/task-2547-dev6-postupdate origin/main 2>/dev/null || true cd .worktrees/task-2547-dev6-postupdate git fetch origin task/task-2547-dev6-clean git checkout FETCH_HEAD python -m pytest tests/regression/test_task_id_parser_v3.py tests/regression/test_dispatch_hardening_2487.py tests/regression/test_dashboard_report_hardening_2487.py tests/regression/test_dotphase_compat_2487.py tests/regression/test_legacy_compat_2487.py tests/regression/test_scripts_hardening_2487.py -v # 어설션: 57/57 PASS (이전 stale base revalidation에서 PASS, update-branch 후에도 PASS) ``` regression FAIL → ESCALATED (Critical 7 #2 EFFECTIVE_DIFF_FAIL) ## §8. CI all SUCCESS 재확인 (회장 §명시 18단계 #8) ```bash # update-branch 후 CI 재실행 트리거됨. 대기 후 확인. for i in 1 2 3 4 5 6; do # 최대 30분 대기 (5분 간격) STATUS=$(GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view 95 --repo Jeon-Jonghyuk/dev_workspace \ --json statusCheckRollup \ --jq '[.statusCheckRollup[] | {name, conclusion, status}]') PENDING=$(echo "$STATUS" | jq '[.[] | select(.status != "COMPLETED")] | length') FAILED=$(echo "$STATUS" | jq '[.[] | select(.conclusion != null and .conclusion != "SUCCESS" and .conclusion != "NEUTRAL" and .conclusion != "SKIPPED")] | length') echo "iter=$i pending=$PENDING failed=$FAILED" if [ "$PENDING" -eq 0 ] && [ "$FAILED" -eq 0 ]; then echo "CI all SUCCESS"; break fi if [ "$FAILED" -gt 0 ]; then echo "CI FAIL detected"; echo "$STATUS"; exit 1 fi sleep 300 done ``` 어설션: 11/11 SUCCESS (gemini-review-gate / phase3-merge-gate 포함). ## §9. Gemini evidence commit_id == 새 head SHA 확인 (회장 §명시 18단계 #9) ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh api repos/Jeon-Jonghyuk/dev_workspace/pulls/95/reviews \ --jq '[.[] | select(.user.login | test("gemini|google-labs"; "i"))][-1] | {state, submitted_at, commit_id}' ``` - 최근 Gemini review commit_id == NEW_HEAD → FRESH → §11 - 최근 Gemini review commit_id == 90cadf5fd3ed (이전 head) → STALE → §10 ## §10. Gemini evidence stale 시 OWNER /gemini review 1회 요청 (회장 §명시 18단계 #10) Gemini evidence stale 확인되면: - **봇은 /gemini review trigger X** (GEMINI_EXTERNAL_TRIGGER_GAP — bot-opened PR auto trigger 미작동) - **ESCALATED 보고** with classification = `GEMINI_EXTERNAL_TRIGGER_REQUIRED_AFTER_UPDATE_BRANCH` - 회장에게 OWNER /gemini review 1회 요청 - 다음 cron에서 finalize 재개 가능 본 cron에서 §10 이상 진행 불가 시 즉시 ESCALATED 보고 종료. §11~§17 미실행. ## §11. unresolved 0 확인 (회장 §명시 18단계 #11) ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh api graphql -f query=' { repository(owner: "Jeon-Jonghyuk", name: "dev_workspace") { pullRequest(number: 95) { reviewThreads(first: 50) { nodes { id, isResolved, comments(first:1) { nodes { body, author { login } } } } } } } }' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length' ``` 어설션: 0건. - unresolved code_changing 있으면 ESCALATED (Critical 7 #6 SELF_POLICY_REPLACEMENT_CHAIN_LIMIT_HIT, 자동 task-2547+1 발행 X) - unresolved style_only는 reply + resolve (same-PR push 0) ## §12. mergeStateStatus CLEAN 확인 (회장 §명시 18단계 #12) ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view 95 --repo Jeon-Jonghyuk/dev_workspace --json mergeStateStatus,mergeable ``` 어설션: mergeStateStatus=CLEAN, mergeable=MERGEABLE. ## §13. BOT_GITHUB_TOKEN identity 재확인 (회장 §명시 18단계 #13) ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh api /installation/repositories --jq '.repositories[0].full_name' 2>&1 ``` 어설션: installation token (app/jeon-jonghyuk-taskctl-bot). ## §14. BOT squash merge (회장 §명시 18단계 #14) ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh pr merge 95 \ --repo Jeon-Jonghyuk/dev_workspace \ --squash --auto=false --delete-branch=false ``` mergedBy 직접 검증: ```bash GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view 95 --repo Jeon-Jonghyuk/dev_workspace --json mergedBy,mergedAt,mergeCommit \ --jq '{mergedBy: .mergedBy.login, mergedAt, mergeCommit: .mergeCommit.oid[0:12]}' ``` 어설션: mergedBy=`app/jeon-jonghyuk-taskctl-bot`. 다르면 BOT_IDENTITY_DIVERGENCE 박제. ## §15. post_merge_smoke_runner 실행 (회장 §명시 18단계 #15) ```bash cd /home/jay/workspace git fetch origin main python3 -c " from anu_v2.post_merge_smoke_runner import PostMergeSmokeRunner runner = PostMergeSmokeRunner() result = runner.run_post_merge_smoke(task_id='task-2547', merge_commit='') print(result) " ``` 어설션: smoke PASS, marker `memory/events/task-2547.smoke-evidence` 생성. ## §16. reconcile evidence 생성 (회장 §명시 18단계 #16) ```bash python3 /home/jay/workspace/utils/lifecycle_reconciliation_manager.py \ --reconcile --task-id "task-2547" --apply --json ``` 어설션: state=FINALIZED, marker `memory/events/task-2547.reconcile-evidence` 생성. ## §17. done.acked / merge-done 정리 (회장 §명시 18단계 #17) - `memory/events/task-2547.done` 보존/생성 - `memory/events/task-2547.done.acked` 생성 - `memory/events/task-2547.merge-done` 생성 ## §18. PR #49/#50/#51/#52 head unchanged 재확인 + lineage 갱신 (회장 §명시 18단계 #18) ```bash for n in 49 50 51 52 92 93 94 96; do GH_TOKEN=$BOT_GITHUB_TOKEN gh pr view $n --repo Jeon-Jonghyuk/dev_workspace --json state,headRefOid \ --jq "{pr:$n, state:.state, head:.headRefOid[0:12]}" done ``` 어설션: - PR #49 head=`da9a8e1265fb` unchanged + OPEN - PR #50 head=`464114bfbcf9` unchanged + OPEN - PR #51 head=`0d388b989111` unchanged + OPEN - PR #52 head=`b341e8747eb7` unchanged + OPEN - PR #92 head=`15cf6ad011e1` unchanged + OPEN - PR #93 head=`5ad46d999988` unchanged + OPEN - PR #94 head=`2996be2565ec` unchanged + OPEN - PR #96 state=MERGED + head=`429569cdb1e5` lineage 갱신 (`memory/events/task-2547.replacement-lineage.json`): ```python import json p = "/home/jay/workspace/memory/events/task-2547.replacement-lineage.json" data = json.loads(open(p).read()) prior = data.pop("dispatch_status", None) if "dispatch_status_history" not in data: data["dispatch_status_history"] = [] if prior: data["dispatch_status_history"].append(prior) data["dispatch_status"] = { "stage": "REPLACEMENT_PR_FINALIZED_VIA_OPTION_B_UPDATE_BRANCH", "outcome": "MERGED", "ts": "", "retry_index": len(data["dispatch_status_history"]), "previous_head": "90cadf5fd3eda572ab28fe53ea4321285f5af2e8", "new_head_after_update_branch": "", "update_branch_method": "MERGE", "merge_commit": "", "merged_by": "app/jeon-jonghyuk-taskctl-bot", "smoke_pass": True, "reconcile_evidence_path": "memory/events/task-2547.reconcile-evidence", "owner_gemini_trigger_after_update_branch": "", "owner_gemini_trigger_completed_at": "", "serial_order_predecessor_pr": 96, "serial_order_predecessor_merge_commit": "2bae7b7bd8f9fb816c6dcf2fef35d452529ce8fc", "ruleset_policy_respected": True, "admin_override_used": False } open(p, "w").write(json.dumps(data, indent=2, ensure_ascii=False) + "\n") ``` ## 금지 14건 (회장 §명시 1:1) 1. ❌ ruleset 완화 2. ❌ admin override 3. ❌ owner PAT 4. ❌ default GH_TOKEN fallback 5. ❌ force push 6. ❌ rebase 7. ❌ empty commit 8. ❌ close/reopen 9. ❌ expected_files amendment 10. ❌ PR #49/#50/#51/#52 변경 11. ❌ PR #95에 code-changing commit 추가 12. ❌ bot /gemini review 13. ❌ md/report만으로 PASS 처리 14. ❌ long polling / self-register 반복 ## 보고 ### MERGED 보고 필수 12항목 (회장 §명시 1:1) 1. new head SHA 2. mergedAt 3. mergedBy = `app/jeon-jonghyuk-taskctl-bot` 4. effective diff == expected_files 18 5. forbidden path 0 6. CI all SUCCESS 7. Gemini evidence fresh 8. unresolved 0 9. mergeStateStatus CLEAN 10. smoke-evidence 존재 11. reconcile-evidence 존재 12. PR #49/#50/#51/#52 head unchanged ### ESCALATED 보고 필수 5항목 (회장 §명시 1:1) 1. Critical 7 번호 2. exact blocking gate (§N 어느 단계) 3. new head SHA (update-branch는 이미 수행되었으므로) 4. PR #49/#50/#51/#52 head unchanged 여부 5. forbidden action 0 여부 6. 다음 owner action ESCALATED 매핑 가이드: - §1/§2 update-branch 실패 → #6 UPDATE_BRANCH_FAILED - §4/§5 effective diff 변동 → #2 EFFECTIVE_DIFF_FAIL - §6 forbidden path 발견 → #1 FORBIDDEN_PATH - §7 regression FAIL → #2 EFFECTIVE_DIFF_FAIL (호환 실패) - §8 CI FAIL → #6 CI_FAILURE_AFTER_UPDATE_BRANCH - §9 Gemini evidence stale → #6 **GEMINI_EXTERNAL_TRIGGER_REQUIRED_AFTER_UPDATE_BRANCH** (OWNER /gemini review 1회 요청, operational nudge) - §11 unresolved code_changing → #6 SELF_POLICY_REPLACEMENT_CHAIN_LIMIT_HIT (OWNER_DECISION_REQUIRED) - §12 mergeStateStatus 비CLEAN → #6 MERGE_STATE_NOT_CLEAN_AFTER_UPDATE_BRANCH - §14 mergedBy 불일치 → BOT_IDENTITY_DIVERGENCE ## 최종 원칙 Option B = ruleset 일관성 + PR 보존 + chain 추가 0. update-branch는 PR branch에 main을 merge → 새 head SHA → ruleset BEHIND 정책 해소. Gemini stale 시 OWNER trigger 1회 후 finalize 재개. 금지 14건 1:1 준수. MERGED 시 task-2487 series clean replacement 종결 + task-2530/2543/2502 main 통합 검증 완료. ESCALATED 시 회장 결정 대기.