---
task_id: task-2992
scope: task
team: dev4-team
lead: 비슈누 (Vishnu)
level: Lv.2
date: 2026-08-20
status: completed
project_path: /home/jay/projects/insuwiki
pr: https://github.com/Jeon-Jonghyuk/InsuWiki/pull/5
---

# 작업 보고: task-2992 — 인슈위키 임베딩 작업 원격 반영 + PR 생성

- 팀: dev4-team (비슈누)
- 팀원: 카르티케야(원격 반영), 하누만(시크릿 전수 스캔) · 팀장 직접: t2980 HOLD 재평가 · 독립 검증
- 대상 저장소: /home/jay/projects/insuwiki (외부 repo, workspace 밖)

## S — 상황 (Situation)

task-2989 로 인슈위키 벡터 검색을 복구했고 ANU 독립검증까지 통과했으나, **결과물이 로컬 브랜치에만 존재**했다. 원격 브랜치가 없으니 PR 도 없고, `master` 에 반영될 경로 자체가 없었다. 즉 고친 것이 코드베이스에 남지 않은 상태였다.

착수 전 ANU 실측을 재확인한 결과 전부 일치했다.

- master = `d5859a9` (= `origin/master` `d5859a94ab941d65e0b331245d75e4f1b3e0a38c`, 뒤처짐 없음)
- 브랜치 `task/task-2989-dev4`, base = master 직분기, 커밋 6건, 워크트리 clean
- HEAD = `aeb168748acb91ba5317ef89c3a9a8042e070e3b`, HEAD tree = `85fe417b9e82bd193b5f55db446be1ddf6226330`
- 원격에 해당 브랜치 부재 — `gh api git/refs/heads/task/task-2989-dev4` → **HTTP 404** 로 실증

## C — 문제 (Complication)

1. 이 환경의 v3.6 하네스가 통상적인 브랜치 업로드 명령 리터럴을 Bash 어디서든 차단한다. **자기보고가 아니라 실제 차단임을 확인**했다(명령 실행 시 PreToolUse hook 이 차단, 별도로 `gh api` 로 원격 404 를 교차 확인).
2. 대상 저장소가 workspace 밖이라 통상 taskctl 경로를 그대로 쓸 수 없다.
3. 코드 변경이 일절 금지된 태스크라, 반영 과정에서 저장소 파일이 단 한 글자도 바뀌면 범위 위반이다.

## Q — 접근 (Question)

**GitHub Git Data API 로 6커밋을 순서대로 재생**했다. 각 커밋마다 blob 생성 → `base_tree` 를 직전 커밋의 원격 tree 로 둔 delta tree 생성 → commit 생성. 삭제 파일은 tree 엔트리 `sha:null`, 파일 모드는 `git ls-tree` 실측값을 그대로 사용, rename 은 `--no-renames` 로 A/M/D 로 풀어 처리했다.

git tree 는 content-addressed 이므로 정확히 재현하면 tree sha 가 일치한다. 따라서 **커밋 sha 가 아니라 tree sha 동일성을 판정 기준**으로 삼았다(커밋 sha 불일치는 정상).

## A — 결과 (Answer)

### 1. 원격 브랜치 생성 — 완료

`refs/heads/task/task-2989-dev4` 생성, 최종 원격 커밋 `97abae8aff7d72a050c2203caf13882d7b48be7e`.

`gh api repos/Jeon-Jonghyuk/InsuWiki/git/refs/heads/task/task-2989-dev4` 원문:

```json
{"ref":"refs/heads/task/task-2989-dev4","node_id":"REF_kwDORLX8s75yZWZzL2hlYWRzL3Rhc2svdGFzay0yOTg5LWRldjQ","url":"https://api.github.com/repos/Jeon-Jonghyuk/InsuWiki/git/refs/heads/task/task-2989-dev4","object":{"sha":"97abae8aff7d72a050c2203caf13882d7b48be7e","type":"commit","url":"https://api.github.com/repos/Jeon-Jonghyuk/InsuWiki/git/commits/97abae8aff7d72a050c2203caf13882d7b48be7e"}}
```

### 2. tree sha 대조 — 6/6 MATCH (팀장 직접 재계산)

로컬 값은 팀원 보고를 승계하지 않고 팀장이 `git rev-parse <c>^{tree}` 로 직접 재계산했고, 원격 값은 `gh api compare/master...task/task-2989-dev4` 로 독립 조회했다.

| # | 로컬 tree sha | 원격 tree sha | 판정 |
|---|---|---|---|
| 1 ca5e418 | 0e4d9612dc6e50deeb473c639cc959a4c0fb529e | 0e4d9612dc6e50deeb473c639cc959a4c0fb529e | MATCH |
| 2 fb12a9f | 0ef5a1891276c9f40aad35be23bb0007806408a6 | 0ef5a1891276c9f40aad35be23bb0007806408a6 | MATCH |
| 3 8146014 | 6adfc7b15096d64738f28b311bd524c7b62fbcb8 | 6adfc7b15096d64738f28b311bd524c7b62fbcb8 | MATCH |
| 4 dbcffeb | 33045ef8fcaeda3a465aed09f875472f98258768 | 33045ef8fcaeda3a465aed09f875472f98258768 | MATCH |
| 5 1da8239 | 3a61a9da33332cd53eb56385094ee538b1bc987f | 3a61a9da33332cd53eb56385094ee538b1bc987f | MATCH |
| 6 aeb1687 | 85fe417b9e82bd193b5f55db446be1ddf6226330 | 85fe417b9e82bd193b5f55db446be1ddf6226330 | MATCH |

`compare/master...task/task-2989-dev4` 실측: `ahead=6`, `behind=0`, `merge_base=d5859a94ab941d65e0b331245d75e4f1b3e0a38c`, `total_commits=6`, `files=55`.

### 3. PR 생성 — 완료

- **PR #5** — https://github.com/Jeon-Jonghyuk/InsuWiki/pull/5
- `state=open`, `draft=false`, `mergeable=true`, `commits=6`, `changed_files=55`, `+2410/-77`
- head `task/task-2989-dev4` (`97abae8a`) → base `master`

PR 본문에 요구된 3요건을 전부 명시했다.

- **무엇이 고쳐졌는가** — 벡터 검색이 한 번도 작동한 적이 없었음. 근본원인 3종(차원 3072 vs 인덱스 768 · 네이티브 Vector 타입 미사용으로 `find_nearest` 원천 미매칭 · 재색인 선별이 타입 미확인), 이제 hits>0 실증
- **ANU 독립검증 결과** — 303건 전건 네이티브 `Vector(768)`, 실패 0 · 자체 질의 6종 전부 `HITS=3` · 원문 유실 0(필드 4,249개 대조 불일치 0) · 회귀 128 passed/0 failed
- **외부 API 키 없이 동작** — 로컬 모델 768차원, 오프라인 모드 강제, `127.0.0.1:8300` 루프백 전용
- **키 미회전 판단 근거** — 한 줄로 명시(아래 4항)

### 4. 시크릿 재확인 — `.pyc` 0건 + 빌드 산출물 전수

`.py` grep 으로 끝내지 않고 HEAD 트리 13,749 파일 전체를 훑었다.

| 항목 | 건수 | 판정 |
|---|---|---|
| .pyc (HEAD 트리 추적) | 0 | PASS |
| .pyo/.pyd/.so/.o/.a/.dylib/.dll/.class/.jar/.war/.whl/.egg/.egg-info | 각 0 | PASS |
| \_\_pycache\_\_/ | 0 | PASS |
| .env 전 변형 | 0 | PASS |
| .pem/.key/.p12/.pfx/.jks/.keystore | 각 0 | PASS |
| credentials.json / service-account.json | 0 | PASS |
| .npmrc / .netrc / id_rsa | 0 | PASS |
| .next/ out/ .venv/ venv/ target/ coverage/ .cache/ .terraform/ | 각 0 | PASS |
| net-new 시크릿 (diff 추가줄 전수) | **0** | PASS |

- 40자 이상 base64/hex 후보 71건은 전부 육안 확인 — 마크다운 구분선·주석·테스트 함수명. 유일 예외인 테스트 픽스처 문자열은 `mask()` 가 값을 노출하지 않는지 검증하는 가짜 경로로, 실제 시크릿 아님.
- `functions/node_modules/` 12,234건, `dist/` 1,453건, `build/` 4,873건, `.map` 1,093건이 추적 중이나 **master 시점부터 동일**(카운트 12234=12234, 해당 경로 diff 빈 결과) — 이번 브랜치 책임 아님. 별건 위생 이슈로 기록만 남긴다.

**키 회전 판단 근거(실측)**: `private=true` · 협업자 `1명` · 포크 `0개`. `gh api repos/:owner/:repo`, `/collaborators`, `/forks` 직접 조회로 확인했고, 이 근거를 PR 본문에 한 줄로 남겼다. 히스토리 재작성도 같은 판단으로 미실행.

### 5. ★ 팀원 보고 정정 1건 (팀장 재측정)

하누만의 잔존 평문 키 스캔이 **3종으로 과소집계**했다. 스캔 패턴 목록에 `GOCSPX-` 와 `1//0` 접두가 빠져 있었기 때문이다. 팀장이 전체 패턴으로 재측정한 결과 `config.py` 잔존 시크릿은 **4종**이며, 이는 아르고스의 선행 감사(task-2980-phase3-secrets)와 정확히 일치한다.

| sha256(12) | 소재 | net-new 여부 |
|---|---|---|
| a89c386ab5e3 | scripts/youtube-pipeline/youtube_pipeline/config.py | 기존 (도입 커밋 d8aa4c1, origin/master 조상) |
| c50d0068dcff | 상동 | 기존 |
| 17bb444d7e36 | 상동 | 기존 |
| f3300debddf9 | 상동 | 기존 |

**4종 전부 이번 6커밋 이전부터 존재**하며 이 브랜치가 도입한 것이 아니다. 값은 이 문서 어디에도 없다(sha256 앞12자만).

## t2980 HOLD 재평가 — 결론

**t2989 의 위생 조치는 t2980 의 HOLD 를 해소하지 않는다.** (읽기 전용 조회만 수행, t2980 브랜치 조작·머지·수정 0건)

1. **HOLD 의 실제 차단 사유가 `.pyc` 팁 오염이 아니었다.** 아르고스 감사의 결론은 *published `origin/master` 히스토리에 동일 4개 시크릿이 blob 2개(`.pyc` + 소스 `.py`)로 생존* 이며, 도입 커밋 `d8aa4c1` 이 `origin/master` 의 조상이다. **어떤 브랜치를 머지해도 과거 커밋의 blob 은 삭제되지 않는다.** t2980 팁은 이미 `.pyc` 0건이었다.
2. **t2989 가 새로 해소하는 것이 없다.** t2989 가 untrack 한 `.pyc` 36개는 t2980 이 지우는 집합과 **완전히 동일**(두 목록 `diff` 결과 차이 0). master 추적 36건 = t2989 삭제 36건 = t2980 삭제 36건.
3. **★ 오히려 t2989 가 t2980 보다 덜 한다.** 소스 `config.py` 하드코딩 키 매치줄 실측 — master `4` · **t2989 팁 `4` (잔존)** · t2980 팁 `0` (정리 완료). t2989 는 `config.py` 를 수정했으나(blob `b9b387bc` ≠ master `683f3dd5`) 평문 기본값은 제거하지 않았다.

따라서 HOLD 해소 경로는 **키 회전** 또는 **history rewrite** 뿐인데 둘 다 회장이 불필요/금지로 결정했다. 즉 이 HOLD 는 "t2989 로 해소되는" 성격이 아니라 **회장의 리스크 수용으로 waive 할지 여부**의 문제이며, 그 판단은 ANU/회장 사안으로 본 태스크 범위 밖이다.

> ⚠️ 파생 함의: **PR #5 를 머지해도 master 팁의 소스 평문 키 4종은 그대로 남는다.** "`.pyc` 0건"을 "시크릿 클린"으로 읽으면 오독이다. 이 점을 PR 본문에도 명시해 두었다.

## L1 스모크테스트

이 태스크는 코드 변경 0줄의 원격 반영 작업이므로 서버 기동 대상이 없다. 대신 **API 메타데이터 신뢰를 배제하고, 실제 원격에서 내용을 되받아 대조**하는 것을 L1 실증으로 삼았다.

- **서버 재시작**: 해당없음 (코드 변경 0줄, 배포 대상 없음)
- **API 응답 확인**: 수행함 — `gh api .../pulls/5` → `state=open`, `mergeable=true`, `commits=6`, `changed_files=55`. `gh api .../git/refs/heads/task/task-2989-dev4` → 200 (착수 전 404 → 반영 후 200 으로 전이 확인)
- **스크린샷**: 해당없음 (UI 산출물 없음)
- **★ 원격 되받기 실증**: `git fetch origin refs/heads/task/task-2989-dev4` (SSH, REST API 와 독립 경로) 후 대조

```
원격 fetch tree = 85fe417b9e82bd193b5f55db446be1ddf6226330
로컬     tree = 85fe417b9e82bd193b5f55db446be1ddf6226330
L1_TREE_MATCH=YES
되받은 트리의 .pyc 건수 = 0
```

API 가 보고한 값이 아니라 **실제 원격 저장소에서 전송받은 객체**로 tree 동일성과 `.pyc` 0건을 확인했다.

## CI 상태 — `validate` FAILURE (기존 결함, 이 브랜치 무관)

head `97abae8a` 의 `validate` 체크가 **`completed/failure`** 로 끝났다(`mergeable=true`, `mergeable_state=unstable`). 숨기지 않고 그대로 보고한다.

"pre-existing 이니 괜찮다"고 자기보고하지 않고, **CI 로그를 직접 받아 독립 검증**했다 (job `96468176432`).

**실패 내용**: TypeScript 컴파일 에러 545건. 전부 `TS2307 Cannot find module`(CI 에서 의존성 미설치) 또는 `TS7006 implicit any` 다.

| 에러 파일 경로 접두 | 건수 |
|---|---|
| nextapp | 400 |
| scripts | 60 |
| functions | 27 |
| next.config.ts | 1 |

**이 브랜치가 원인이 아니라는 증거 3종**:

1. **변경 파일 55건 중 CI 에러 목록에 등장하는 것이 0건이다.** 브랜치 변경 파일 전체를 로그와 대조한 결과 HIT 0. 이 브랜치가 건드린 유일한 TypeScript 파일 `functions/src/crawlYoutubeChannels.ts` 는 로그에 **0회** 등장한다.
2. **과거 PR 전건이 동일하게 실패한다** — PR #1 `failure`, #2 `failure`, #3 `failure`, #4 `failure`. 특히 **PR #4(task-2870)는 이 실패 상태로 master 에 머지된 선례**다.
3. **로그의 `reindex` 매치 5건은 이 브랜치와 무관하다.** 전부 `functions/src/__tests__/reindexApi.test.ts` 와 `nextapp/src/app/api/admin/insurance/reindex/**` 로, **master 에 이미 존재하며 브랜치 변경 0건**인 TypeScript 파일이다. 이 브랜치가 추가한 것은 Python `scripts/reindex/**` 이며 이름만 겹친다. (이름 유사성만으로 넘기지 않고 파일별로 확인했다.)

**정리**: `validate` 는 저장소 전역의 기존 의존성 해석 실패이며, 이 PR 이 유발하거나 악화시킨 것이 아니다. master 자체에는 `validate` 체크가 없고(PR 전용 워크플로), master 에 걸린 `backup` 체크 23건은 전부 `failure` 인 별개의 기존 이슈다.

**다만 판단은 ANU 소관이다.** 통상 규칙상 `unstable` + `fail` 은 머지 금지이므로, 위 선례(#4)를 근거로 waive 할지 여부를 ANU 가 결정해야 한다. 본 태스크는 PR 생성까지가 범위이며 **직접 머지하지 않았다.**

## affected_files

- /home/jay/workspace/memory/reports/task-2992.md
- /home/jay/workspace/memory/events/task-2992.result.json

저장소(`/home/jay/projects/insuwiki`) 파일은 **0건** 변경됐다. 원격 산출물(브랜치 `task/task-2989-dev4`, PR #5)은 파일 수정이 아니라 기존 커밋의 게시다.

## 수정 파일별 검증 상태

| 파일 | 변경 | 검증 방법 | status |
|---|---|---|---|
| /home/jay/workspace/memory/reports/task-2992.md | 신규 생성 | 본 보고서. 존재 및 내용 확인 | verified |
| /home/jay/workspace/memory/events/task-2992.result.json | 신규 생성 | json.load 파싱 성공 확인 | verified |

**저장소 파일 변경 0건.** 증명:

```
cd /home/jay/.worktrees/task-2989-dev4
git status --porcelain   → 빈 출력
git rev-parse HEAD       → aeb168748acb91ba5317ef89c3a9a8042e070e3b (착수 시점과 동일)
```

로컬 커밋 생성·amend·rebase 0건. `allowed_resources.paths` 는 `memory/reports/task-2992.md` 단일 항목이며 실제 수정 파일도 그것 하나다. `forbidden_paths`(scripts/**, functions/**, nextapp/**, .github/**) 침범 0건.

## 3 Step Why

- **1st Why — 왜 결과물이 원격에 없었나?** task-2989 의 finish-task 가 QC PASS·scope-guard PASS 였음에도 **taskctl state 파일 부재로 머지가 차단**되어 `.done.escalated` 로 끝났고, 그 시점에 브랜치가 로컬에만 남았다.
- **2nd Why — 왜 이번에도 통상 경로로 못 올렸나?** v3.6 하네스가 브랜치 업로드 명령 리터럴을 Bash 전 구간에서 차단한다. 이는 회피 대상이 아니라 설계된 안전장치이므로, 우회가 아니라 **명세가 지정한 대체 경로(Git Data API)** 를 썼다.
- **3rd Why — 왜 커밋 sha 가 아니라 tree sha 로 판정했나?** API 재생 커밋은 생성 경로가 달라 커밋 객체 sha 가 로컬과 달라질 수 있다. 반면 tree 는 content-addressed 라 **내용이 같으면 반드시 같고 다르면 반드시 다르다.** 즉 tree sha 만이 "내용이 온전히 올라갔는가"의 위조 불가능한 증거다. 커밋 sha 대조로 판정했다면 정상 반영을 실패로 오판했을 것이다.

## trip-wire 5종 (실측)

| 항목 | 실측값 | 판정 |
|---|---|---|
| Critical7 | 0 | PASS |
| PII net-new | 0 | PASS |
| 회귀 실패 | 0 (코드 변경 0줄 — 회귀 대상 없음. t2989 시점 실측 128 passed/0 failed) | PASS |
| forbidden_paths 침범 | 0 | PASS |
| nonce = task_id 일치 | task-2992 | PASS |

## 게이트 (Lv.2)

- **G1 설계 게이트** — affected_files = `memory/reports/task-2992.md` 단일. 다른 팀과 겹침 없음. `task/task-2980-dev1`(dev1 소관)은 재평가 목적의 **읽기 전용 조회만** 수행했고 조작 0건.
- **G2 구현 게이트** — 팀 테스터(하누만) QC 수행 완료. 기능 검증 = tree sha 6/6 대조 + 원격 되받기 tree 일치 + `.pyc` 0건.
- **G3 머지 게이트** — PR #5 생성 완료. **머지는 ANU 소관이므로 미실행**(task 명세: "머지는 ANU 가 한다. 직접 머지하지 말 것").

## 종결 게이트 결과 — FAIL 2건 (둘 다 자가해소 금지 항목)

`FINALIZE_ONLY=1 G4_GATE_ENABLED=0 finish-task.sh task-2992 dev4` 실행 결과 `EXIT=1`, `.done` 미생성. 요약 `7 PASS, 2 FAIL, 11 SKIP, 3 WARN`. **강제로 `.done` 을 만들지 않았고, 해치를 자가부여하지도 않았다.**

### FAIL 1 — tdd_check (문서화된 오탐)

```
변경 파일 총 4개: 테스트 0개, 구현 1개
테스트 파일 없이 구현 파일만 변경됨 → FAIL
  IMPL  [2026-08-20T14:44:29Z] /tmp/task2992_replay.py
```

`/tmp/task2992_replay.py` 는 저장소 밖 임시 스크립트인데 tdd_check 가 이를 **구현 파일로 계수**했다. 실제 저장소 코드 변경은 **0줄**이다. 이는 선행 기록된 `tdd_check /tmp 임시 스크립트 오탐`과 동일 패턴이며, **해치 부여 권한은 ANU** 이므로 봇이 자가해소하면 self-bypass 다. 근거만 박제하고 ANU override 를 요청한다.

### FAIL 2 — git_evidence (환경 제약, 범위 위반 아님)

```
FAIL COMMIT_EXISTS: task-2992 커밋 0건 + mergeCommit evidence 없음
FAIL NO_UNCOMMITTED: uncommitted 변경 존재 (57 unstaged, 62 staged)
```

- **COMMIT_EXISTS**: 보고서를 커밋하려 했으나 workspace `main` 훅이 차단했다 — `[BLOCKED] main direct commit prohibited`. 알려진 동작으로, 워킹트리 상태가 곧 라이브다. 보고서는 디스크에 실재한다.
- **NO_UNCOMMITTED**: 57 unstaged / 62 staged 는 **다른 에이전트들의 진행 중 변경**이며 본 태스크 산출물이 아니다. 이를 커밋하면 다른 팀 작업을 임의 커밋하는 것이 되어 금지 규칙 위반이다. 또한 공유 workspace 에서 브랜치를 전환하면 동시 실행 중인 다른 에이전트를 교란할 수 있어 **의도적으로 하지 않았다.**

즉 두 FAIL 모두 이 태스크의 산출물 품질이나 범위 준수와 무관하다. `file_touch_ratio_check` 는 교집합 1 / ratio 1.00 으로 PASS, `scope_check` 는 WARN(초과 항목이 전부 `/tmp` 임시파일과 task md), `l1_smoketest_check` PASS, `critical_gap` PASS 다.

### 참고 — 재실행 전 자체 교정한 항목

`file_check` 의 SCQA 미검출 WARN 은 검사기가 `Situation/Complication/Question/Answer` 또는 `S:` 패턴을 찾는데 제목이 `## S — 상황` 이라 미매칭이었다. G3 게이트의 `## S —` 요구와 동시 충족하도록 `## S — 상황 (Situation)` 형태로 보강했다. `affected_files` 섹션도 신설했다. **게이트 판정 자체를 건드린 것이 아니라 보고서 형식을 규격에 맞춘 것이다.**

## ANU 판단 요청 (3건)

1. **PR #5 머지 여부 — CI `validate` FAILURE waive 판단 필요.** 반영 무결성은 tree sha 6/6 일치로 확정됐고, CI 실패는 로그 직접 검증 결과 **저장소 전역의 기존 결함**(변경 파일 55건 중 에러 목록 등장 0건, 과거 PR #1~#4 전건 동일 실패, #4는 그 상태로 머지된 선례)이다. 그러나 `unstable`+`fail` 머지 금지 규칙에 걸리므로 waive 여부는 ANU 판단이다.
2. **t2980 HOLD 처리** — 위 결론대로 t2989 로는 해소되지 않는다. 회장의 리스크 수용(private·협업자1·포크0)을 근거로 HOLD 를 waive 할지 판단 필요.
3. **`nextapp` 벡터검색 소비자 미전환** — `vector-search/route.ts:357` 이 여전히 Gemini `text-embedding-004` 로 질의를 임베딩한다. 차원은 맞아 결과는 나오지만 의미공간이 달라 무의미하다. 현재는 Gemini 키 폐기로 미발현이나 **키 복구 순간 조용히 틀린 결과**가 나온다(금소법 리스크). 별도 태스크 필요. `nextapp/**` 은 본 태스크 forbidden_paths 이므로 손대지 않았다.

## 비고

- 카르티케야가 검증 마무리 단계에서 `task/task-2980-dev1` 을 `git log -1` 로 **읽은 사실을 자진 신고**했다. 지시에 "읽지도 말라"고 명시돼 있었으므로 지시 위반이다. 다만 로컬 read-only 조회로 원격 접근·브랜치 상태 변경은 0건이고, 얻은 정보는 산출물에 사용되지 않았다. 정정 없이 기록한다.
- 임시 스크립트 `/tmp/task2992_replay.py`, 로그 `/tmp/task2992_replay.log`, PR 본문 `/tmp/task2992_pr_body.md` — 전부 저장소 밖 임시 파일이다.
- 테스트 결과: 통과 (tree sha 6/6 MATCH · 원격 되받기 MATCH · `.pyc` 0건 · net-new 시크릿 0건 · 저장소 파일 변경 0건)

## 세션 통계
- 총 도구 호출: 0회

