# task-2493 재검증 evidence (2026-05-08)

## 검증 실행 요약

### [CMD-1] /installation/repositories raw 응답

```
total_count: 8
repository_selection: all
repos + permissions (전체 8개, 대표 3개):
  Jeon-Jonghyuk/my-asset-manager  permissions: {admin:F, maintain:F, push:F, triage:F, pull:F}
  Jeon-Jonghyuk/InsuWiki           permissions: {admin:F, maintain:F, push:F, triage:F, pull:F}
  Jeon-Jonghyuk/dev_workspace      permissions: {admin:F, maintain:F, push:F, triage:F, pull:F}
  (나머지 5개 동일 — 전부 false)
```

**해석**: `/installation/repositories` 의 `permissions` 필드는 collaborator 관점 권한 (해당 설치 주체가 개인 collaborator로서 보유하는 권한)을 표시한다. GitHub App installation token은 collaborator가 아니므로 이 필드는 항상 false로 반환된다. **이것은 App 자체 permissions를 나타내지 않는다.**

---

### [CMD-2] PR #45 상세

```
number: 45
state: closed
merged: True
user.login: JonghyukJeon       ← 사람(User) 계정
user.type: User
merge_commit_sha: 37e26ed447e5cf8f0cd3a45ba1acb524417145d0
head.ref: task/task-2483-dev3
```

**해석**: PR #45는 `JonghyukJeon` (사람)이 생성한 PR이며, 머지도 `JonghyukJeon`이 수행 (이벤트 확인: merged actor.login=JonghyukJeon, type=User, 2026-05-07T13:54:33Z). Bot이 authored 하거나 머지한 PR이 아니다.

→ task-2483 followup.txt의 "PR #45가 commit 37e26ed4로 머지 완료" 기록은 사실이나, **bot authored 아님**.

---

### [CMD-3] merge commit 37e26ed447e5 author/committer

```
sha: 37e26ed447e5
author.login: JonghyukJeon
committer.login: web-flow   (GitHub web-flow = GitHub UI 머지)
commit.author.name: JonghyukJeon
commit.author.email: jonghyuk.jeon@gmail.com
commit.committer.name: GitHub
commit.committer.email: noreply@github.com
```

**해석**: 사람 `JonghyukJeon`이 commit author. GitHub web UI 머지. Bot 개입 없음.

---

### [CMD-4] 최근 closed PR 20개 분석

```
total_closed_recent: 20
bot_authored: 3 (PR #33, #34, #35)
bot_logins: ['jeon-jonghyuk-taskctl-bot[bot]']
sample_bot_pr_numbers: [35, 34, 33]

PR#48 user=JonghyukJeon  type=User   merged=True
PR#46 user=JonghyukJeon  type=User   merged=True
PR#45 user=JonghyukJeon  type=User   merged=True   ← bot 아님
PR#43 user=JonghyukJeon  type=User   merged=True
...
PR#35 user=jeon-jonghyuk-taskctl-bot[bot]  type=Bot  merged=True  ← bot 생성
PR#34 user=jeon-jonghyuk-taskctl-bot[bot]  type=Bot  merged=True  ← bot 생성
PR#33 user=jeon-jonghyuk-taskctl-bot[bot]  type=Bot  merged=True  ← bot 생성
```

**해석**: bot이 실제로 PR을 생성한 이력(PR #33, #34, #35)이 존재한다. 이 PR들은 `POST /repos/{owner}/{repo}/pulls` 호출로만 생성 가능하며, 이는 `pull_requests:write` 권한이 실제 App에 부여돼 있어야 함을 의미한다.

그러나 PR #33, #34, #35의 head commit들을 조회한 결과, **모든 commits의 author.login = JonghyukJeon (사람), email = jonghyuk.jeon@gmail.com** 으로 확인됨. Bot이 branch에 push한 commit 흔적 없음.

---

### [CMD-5] /installation/repositories 헤더

```
X-Accepted-Github-Permissions: allows_permissionless_access=true
X-Github-Api-Version-Selected: 2022-11-28
```

**해석**: `allows_permissionless_access=true` — 이 endpoint는 permission 없이도 접근 가능(public or permissionless). 이 헤더는 이 endpoint의 요구 권한을 나타내며, token이 보유한 권한 목록이 아니다.

---

### [CMD-6] /repos/{owner}/{repo}/installation (App 권한 직접 조회)

```
{"message":"A JSON web token could not be decoded","documentation_url":"...","status":"401"}
```

**해석**: 이 endpoint는 **GitHub App JWT** 로만 호출 가능. Installation token (현재 보유)으로는 불가. App 자체 metadata 조회 불가.

---

### [CMD-7] bot-token-refresh.jsonl 마지막 3행

```
{"ts":"2026-05-07T14:29:27Z","status":"refreshed","sha256_prefix":"245a8aa8de4ad2b4","expires_at":"2026-05-07T15:29:27Z"}
{"ts":"2026-05-07T15:19:30Z","status":"refreshed","sha256_prefix":"23462d60831ff304","expires_at":"2026-05-07T16:19:30Z"}
{"ts":"2026-05-07T16:09:33Z","status":"refreshed","sha256_prefix":"fba78943776c1fc0","expires_at":"2026-05-07T17:09:33Z"}
```

**해석**: 최신 token sha256 prefix = fba78943776c1fc0 (현재 token과 일치). expires_at 2026-05-07T17:09:33Z — 현재(2026-05-08)는 만료됐으나 API 응답은 정상 → 토큰 재발급 후 jsonl 미기록 가능성.

---

### [CMD-8] refresh_bot_token.py 권한 요청 부분

`request_installation_token()` 함수에서 `POST /app/installations/{id}/access_tokens` 호출 시 body = `b""` (빈 본문). 즉 permissions 파라미터를 별도로 지정하지 않음 → **GitHub App에 설정된 전체 permissions 상속**.

권한 제한 파라미터(`"permissions": {...}`) 없음 → 앱에 부여된 최대 권한으로 토큰 발급.

---

## Bot PR 생성 증거 (결정적)

| PR # | user.login | user.type | head.repo | head commits author |
|------|-----------|-----------|-----------|---------------------|
| #35 | jeon-jonghyuk-taskctl-bot[bot] | Bot | Jeon-Jonghyuk/dev_workspace (same, not fork) | JonghyukJeon (사람) |
| #34 | jeon-jonghyuk-taskctl-bot[bot] | Bot | Jeon-Jonghyuk/dev_workspace (same) | JonghyukJeon (사람) |
| #33 | jeon-jonghyuk-taskctl-bot[bot] | Bot | Jeon-Jonghyuk/dev_workspace (same) | JonghyukJeon (사람) |

- Bot이 PR을 생성(POST)했다 → `pull_requests:write` 실제 보유 증거
- Bot이 branch에 push한 commit은 확인 안 됨 (commits 전부 사람 author)
- PR #35는 same repo (fork 아님), head.repo.fork=False → 동일 저장소 브랜치에서 PR

---

## 핵심 오류 교정

### 오류 1: x-accepted-github-permissions 헤더 해석 오류
- **이전 해석**: `x-accepted-github-permissions: pull_requests=read` → bot이 pull_requests:read만 보유
- **정정**: 이 헤더는 해당 endpoint(GET /pulls)가 요구하는 최소 권한을 나타냄. Bot이 read만 가진다는 의미 아님.
- **증거**: GitHub Docs "x-accepted-github-permissions: The permissions that are required to use this endpoint"

### 오류 2: /installation/repositories permissions 해석 오류
- **이전 해석**: `permissions.push:false` → bot이 push 권한 없음
- **정정**: 이 필드는 collaborator permissions로, App installation token에는 항상 false 반환. App 자체 권한과 무관.

### 오류 3: PR #45 evidence 과대평가
- **재검증 요청서의 가정**: PR #45가 bot authored일 것
- **실제**: PR #45 user.type=User (JonghyukJeon). Bot authored PR은 #33, #34, #35.
- **재결론**: PR #45는 bot authored 증거가 아니나, PR #33/34/35가 pull_requests:write의 독립적 증거가 된다.

### 오류 4: contents:write (branch push) 여부
- **현재 확인 가능한 범위**: Bot authored PR #33/34/35의 head commits가 전부 JonghyukJeon authored → bot이 직접 push한 증거 없음.
- **단, PR creation 자체가 가능** → App에 pull_requests:write는 부여돼 있음.
- **contents:write 직접 증거**: commit author email에 bot noreply@ 패턴 없음. 확인 불가.

---

## 판정

### pull_requests:write: **GO (실증됨)**
- PR #33, #34, #35가 bot authored (user.type=Bot) — POST /repos/.../pulls 성공 = write 권한 실증

### contents:write (branch push): **불확실 (증거 부족)**
- Bot authored PR들의 head commits가 전부 사람 author
- Bot이 push한 commit 흔적 없음
- 단, PR head branch가 fork가 아닌 동일 repo → 사람이 push 후 bot이 PR 생성한 워크플로우로 보임

### 종합: 이전 NO-GO 판정의 Step 3/6은 헤더 해석 오류이므로 정정 필요.
Step 5 (push:false)는 collaborator permissions 오해석이므로 정정 필요.
단, contents:write 직접 증거가 없어 완전 GO 판정은 불가.
