# PR #265 observability microfix 착수 packet (코드 착수 전 검토용 · 코드 0)

상태명: `PR265_OBSERVABILITY_MICROFIX_PACKET_REQUESTED_ACTIVE_FALSE`
작성: ANU 직접 (2026-07-04 KST). **착수 packet only — 코드 수정 0 · PR 0 · merge 0 · .github 0 · ruleset 0 · required check 0 · Merge Queue activation 0 · `--publish-check` 0 · check-run 0 · workflow 연결 0 · production activation 0 · ACTIVE=false.** 근거: Stage1 dry-run 관측([[pr265_stage1_dryrun_5f707910_260704]])에서 UNRESOLVED인데 stderr 침묵→어느 tier가 왜 실패했는지 auditor 출력만으로 추적 불가. 상위: [[pr265_axisC_stage1_2_design_260704]].

## 1. 목표 (한 줄)
auditor/resolver가 `MERGE_GROUP_PRS_UNRESOLVED` 또는 gh api 실패를 낼 때, **어느 tier가 무엇을 시도했고 왜 실패했는지**(rc/stderr/api path/accept 거부 사유)를 **verdict JSON(및/또는 stderr)에 남겨** 관측기가 스스로 실패를 설명하게 한다. **순수 additive diagnostics** — 무엇을 resolve하는지·판정 결과는 바꾸지 않는다.

## 2. 현재 구조 사실 (merged main 5f707910)
- `merge_group_pr_resolver.py`:
  - `_gh_api(endpoint)` / `_gh_graphql(query)` → `(rc, data)`. **rc!=0/empty/JSONDecode/timeout/Exception 시 stderr를 capture하나 그대로 버림**(proc.stderr 미사용). 실패가 조용히 삼켜짐.
  - 3-tier: `resolve_via_api`(tier1) → `resolve_via_event_context`(tier2, event 있을 때) → `resolve_via_ref_and_trailers`(tier3). 각 실패 시 `[]` 반환.
  - `resolve_merge_group_prs`: tier1 accept? → tier2 accept? → tier3 accept? → 전부 실패 시 `raise MergeGroupPRsUnresolved`. `_accept`=모든 entry가 pr_number(int>0)+pr_head_sha(non-empty)라야 수용(부분수용 금지·fail-closed). **어느 tier가 시도됐는지·각 gh 호출 rc/stderr·accept 거부 사유 기록 0.**
- `merge_group_evidence_auditor.py`: `audit_merge_group`가 `resolve_merge_group_prs` 호출→예외 시 `status=MERGE_GROUP_PRS_UNRESOLVED`, reason은 "after all 3 tiers"뿐(tier별 내역 없음).

## 3. expected_files 후보 (3개 — 밖으로 번지면 STOP)
- `scripts/merge_group_pr_resolver.py`
- `scripts/merge_group_evidence_auditor.py`
- `tests/regression/test_merge_group_evidence_auditor_2781.py` (또는 신규 회귀 파일)
- ★ 이 3개 밖(다른 utils/.github/gemini gate 등) 수정 필요 시 **즉시 STOP**.

## 4. 설계 범위 (additive diagnostics만)
1. **gh wrapper stderr 노출**: `_gh_api`/`_gh_graphql`가 실패 시 `proc.stderr`(및 rc·endpoint)를 **diagnostics로 반환/기록**. 구현안 2택:
   - (a) 반환 시그니처 `(rc, data)` → `(rc, data, stderr)` 확장 + 호출부 갱신(파급 있음·명시적).
   - (b) 시그니처 유지 + 모듈-레벨/전달식 **diagnostics sink**(list)에 `{tier, api_path, rc, stderr[:N]}` append(파급 최소·권장).
   - → **권고 (b)**: 시그니처 불변으로 회귀 위험 최소.
2. **tier별 시도/실패 기록**: `resolve_merge_group_prs`가 각 tier에 대해 `{tier: "api|event|ref", invoked: bool, entry_count: int, accepted: bool, reject_reason: "empty|incomplete_entry|exception", gh: [{api_path, rc, stderr_snip}]}` 누적.
3. **예외에 diagnostics 부착**: `MergeGroupPRsUnresolved`에 `.diagnostics`(list) 부착 → `audit_merge_group`이 verdict JSON의 신규 **`diagnostics`** 필드에 실어 출력.
4. **출력 채널 제안 (stderr vs JSON)**: **verdict JSON `diagnostics` 필드를 1순위**(redirect로 저장되는 .json에 남아 재현·기계판독 가능). 보조로 stderr에 1줄 요약(사람용) 선택. → **JSON diagnostics 채택 권고**.
5. **관측 대상 repo 노출**: diagnostics에 실제 조회한 `repo` slug를 남겨, DEFAULT_REPO 불일치 같은 config 문제가 표면화되게 함(§6과 연계, 단 slug 값 수정은 안 함).

## 5. ★ 불변 (변경 금지 — 회귀 0 보장선)
- 기존 **status enum 8종 의미 변경 금지** (`PASS`/`MERGE_GROUP_PRS_UNRESOLVED`/... 그대로).
- 기존 **exit code 의미 변경 금지** (0=PASS, 1=그 외).
- **PASS/FAIL 판정 로직·resolve 대상 변경 금지** — diagnostics는 순수 additive(설명만 추가, 결과 불변).
- **resolver가 커버하는 PR 추적 범위 확장(예: `commits/{sha}/pulls` fallback 추가)은 별도 안건으로 분리** — 이 microfix에 포함 금지. (Stage1에서 본 "GitHub은 #265 반환하나 resolver는 UNRESOLVED" = 추적 범위 이슈이지 observability 이슈 아님.)

## 6. DEFAULT_REPO 불일치 분류
- 관측: `DEFAULT_REPO="JonghyukJeon/dev_workspace"`(코드) vs 실 gh slug `Jeon-Jonghyuk/dev_workspace` 불일치.
- 판단: 이건 **observability가 아니라 config correctness** 이슈이고, slug 값 수정은 **cross-environment 영향 가능**(다른 호출부·CI 기본값). → **이번 observability microfix에 포함하지 않음**. 별도 **`PR265_DEFAULT_REPO_SLUG_MISMATCH_CONFIG_HYGIENE_BACKLOG_ACTIVE_FALSE`** 로 분리.
- 단 observability microfix는 diagnostics에 **"조회한 repo slug"를 노출**만 함(값 수정 아님) → 향후 이 불일치가 자동 표면화.

## 7. 테스트 계획
- UNRESOLVED 발생(모든 tier fail) 시 verdict JSON에 `diagnostics`(tier별 시도/거부 사유) 존재 assert.
- gh api 실패 mock(rc!=0 + stderr 주입)에서 diagnostics에 `rc`·`stderr snippet`·`api_path`·`tier` 존재 assert.
- **기존 13 회귀 유지** (status/exit/PASS-FAIL 불변 확인) · PR#263 P1 **36** 유지 · PR#264 **10** 유지 (통합 **59**).
- `--publish-check` 없이도 diagnostics 동작(check-run 생성 0) assert.
- diagnostics 추가가 PASS 케이스의 status/exit/기존 필드를 바꾸지 않음 assert(순수 additive).

## 8. 금지 범위 (재확인)
`.github` 수정 · ruleset/branch protection 변경 · required check 등록 · Merge Queue activation · `--publish-check` 사용 · check-run 생성 · workflow 연결 · production activation · ACTIVE=true · resolver 추적 범위 확장 · repo slug 값 수정 · status/exit/판정 로직 변경 — **전부 금지**.

## 9. STOP_REPORT 조건
- expected_files 3개 밖 수정 필요 · status enum/exit code/PASS-FAIL 로직 변경 필요 · resolver 추적 범위 확장 필요 · repo slug 값 수정이 cross-env 영향 · `.github`/ruleset/required/Merge Queue/`--publish-check` 요구 · 회귀(13/36/10) 깨짐 · activation 표현 등장.

## 10. 산출물 & 다음 (승인 후)
- 승인 시: dev팀 dispatch(ANU 코딩 0) → additive diagnostics 구현 + 회귀 → ANU 독립검증(3파일 갇힘·회귀 59·diagnostics 동작·status/exit 불변·check-run 0) → PR 후보 packet(현재 main 기준 rebase 확인) → 회장 승인 후 PR. **본 단계 = 착수 packet 문서까지만.**

## 결론 (회장 결정 요청)
1. 본 observability microfix 착수 packet 수용 여부.
2. 출력 채널 = **verdict JSON `diagnostics` 필드(권고)** 승인 여부.
3. gh wrapper 구현 = **(b) diagnostics sink(시그니처 불변, 권고)** 승인 여부.
4. DEFAULT_REPO 불일치 = **별도 config hygiene backlog로 분리(권고)** 승인 여부.
5. 승인 시 dev팀 dispatch로 진행할지(ANU 코딩 0).
