# PR #247 Gemini thread resolve packet (evidence-based) — 2026-06-23

**상태명**: `PR247_GEMINI_THREAD_RESOLVE_PACKET_APPROVED_MERGE_NOT_APPROVED_ACTIVE_FALSE`
**성격**: read-only / evidence 정리. 코드 수정·테스트 추가·push·merge·thread 임의 resolve **0**.

## 1. PR #247 head / state / CI (1회 확인, polling 0)
- head = **6fc945b9dde02cb3e1b076d80b9491bd0fd00d38** (6fc945b9)
- base = **main** · state = **OPEN** · mergeable = **MERGEABLE**
- CI = **11/11 pass** — `gemini-review-gate` PASS · `phase3-merge-gate` PASS 포함
- 로컬 working tree content qc sha = **b786d91c** == PR head 추출본 **b786d91c** (동일 반영 확인)

## 2. unresolved Gemini HIGH thread 2개 요약
둘 다 author=`gemini-code-assist`, `resolved=False` / `outdated=False`, path=`teams/content/qc/qc_verify.py`.

- **HIGH thread 1** — line 137 (originalLine 114): "`task_id` 가 검증 없이 파일 경로(`result_path`/`task_md`) 구성에 직접 사용 → `..` 등 traversal 위험. 엄격 alphanumeric 패턴으로 검증하라." (제안: `re.match(r"^[A-Za-z0-9._+-]+$", task_id)` 아니면 `raise ValueError`)
- **HIGH thread 2** — line 185 (originalLine 146): "`allowed_resources.paths` 를 `workspace / p` 로 join → `p` 가 절대경로면 workspace 무시되고 절대경로로 해석되며, `..` 로 workspace 밖 traversal 가능. resolve 후 workspace 내부인지 검증하라." (제안: `(workspace/p).resolve().relative_to(workspace.resolve())`)

## 3. thread별 코드 수정 evidence (head 6fc945b9 실코드)
### HIGH-1 — task_id traversal 방어 (코드 수정 완료)
```python
def run_gate(task_id: str, team: str, workspace: Path) -> dict:
    events = workspace / "memory" / "events"
    events.mkdir(parents=True, exist_ok=True)
    # ★ HIGH-1: task_id 검증 — 파일 경로 구성 전 path traversal 거부.
    tid = str(task_id) if task_id is not None else ""
    if (not tid) or (".." in tid) or ("/" in tid) or (not _SAFE_TASK_ID.match(tid)):
        return _write(events / "invalid-task-id.qc-result", "FAIL", team, {},
                      [f"unsafe task_id 거부(path traversal 방어): {tid[:40]!r}"])
    task_id = tid
    result_path = events / f"{task_id}.qc-result"
```
- `_SAFE_TASK_ID = re.compile(r"^[A-Za-z0-9._+-]+$")` (Gemini 제안 정규식과 동일) + 추가로 `..`/`/` 명시 차단.
- Gemini 제안은 `raise ValueError`, ANU 구현은 **fail-closed structured FAIL**(`_write`, 경로 구성 전 `invalid-task-id.qc-result`) — **동등하거나 더 안전**(crash 대신 구조화 거부, workspace 밖 파일 미생성).

### HIGH-2 — allowed_resources.paths traversal 방어 (코드 수정 완료)
```python
ws_resolved = workspace.resolve()
for p in paths:
    # ★ HIGH-2: 절대경로/travers("..") 거부 + resolve 후 workspace 내부 검증.
    if Path(p).is_absolute():
        ... reasons.append("절대경로 거부(workspace 밖 접근 방어)"); continue
    try:
        fp_resolved = (workspace / p).resolve()
    except Exception as e:
        ... reasons.append("경로 해석 실패"); continue
    if fp_resolved != ws_resolved and ws_resolved not in fp_resolved.parents:
        ... reasons.append("workspace 밖 경로 거부(traversal 방어)"); continue
```
- 절대경로 명시 거부(`Path(p).is_absolute()`) + `resolve()` 후 `ws_resolved not in fp_resolved.parents` 검증.
- Gemini 제안 `relative_to(workspace.resolve())` 와 **동등**(workspace 내부 강제), 추가로 절대경로 선거부 → **동등하거나 더 강함**.

### medium 2개 대응 evidence
- **medium-3** (`_parse_allowed_paths`): allowed_resources 블록 부재 시 전체 폴백 제거(빈 리스트 반환), `paths:` 및 항목 라인 trailing comment(`#`) 무시, 따옴표 우선 추출.
- **medium-4** (산출물 읽기): `fp.is_dir()` → structured FAIL, `read_text` try/except → structured FAIL (traceback crash 방지).

## 4. unresolved 원인 분리 — "코드 미수정" 아님 / "리뷰 thread 상태 미갱신"
- 두 thread 모두 **코드 수정 완료** (위 evidence). 지적 영역이 origLine 114/146 → 현재 line 137/185 로 **shift만** 됐고, GitHub 가 thread 를 `outdated` 로 마킹하지 않아 **stale unresolved** 상태로 남음.
- 즉 **리뷰 thread 상태 미갱신**(GitHub UI 자동 outdated 미발생)이지 코드 미반영 아님. `gemini-review-gate` CI 가 새 head 6fc945b9 에서 PASS 한 것이 코드측 해소의 독립 교차 evidence.

## 5. 테스트 evidence
- 회장 기준 집합(content qc + gate_shadow×2 + goal_contract + v213) = **174 passed**.
- content qc 단독 = **17 passed**.
- **L1 smoke**: 악성 task_id `../../etc/passwd` → `qc_result=FAIL`, reason="unsafe task_id 거부(path traversal 방어)", `invalid-task-id.qc-result` 만 생성(**workspace 밖 파일 미생성** 실증).
- ⚠ 전체 `tests/regression/`(2422 items) 실행 시 **2 errors** — 단 **PR #247 무관 사전존재 파일**: `test_base_source_isolation_2729p9.py`(WORKTREE_ROOT 보호 가드 AssertionError) · `test_orchestration_runtime_2514.py`(`ReplacementPRRunner` ImportError). 본 micro-fix 2파일과 무관.
- 증분 diff = **2파일**(`teams/content/qc/qc_verify.py` · `tests/regression/test_content_qc_2769.py`), expected_files 밖 수정 **0**.

## 6. scope-guard false-positive 축 (Gemini thread resolve 와 별도)
- task-2771+1 `.done` **미생성** 유지 · `failure-envelope` 유지 · 수동 완료 처리 **금지 유지**.
- 원인: scope-guard 가 origin/main..HEAD 누적 8파일 vs 본 task 허용 2파일 비교 → structural false-positive(코드 결함 아님). **이 축은 Gemini thread resolve 와 별개로 기록**.

## 7. 판단 / 권고
- **Gemini HIGH thread 2개 = stale unresolved (코드 수정 완료, 리뷰 thread 상태만 미갱신)**.
- **resolve 권고**: ✅ resolve 가능 후보 (evidence-based: 코드 수정 완료 + CI gemini-review-gate PASS + 테스트 PASS + L1 smoke 실증). 단 **ANU 임의 resolve 금지** — resolve 실행은 owner(회장) 또는 owner 트리거 영역.
- **merge 미승인** 유지. resolve 후에도 merge 는 별도 승인 필요.
- **ACTIVE=false / P0-B FROZEN** 유지. canonical HEAD 38ee0555 불변 · matrix e03059be 불변.
