# v0.1.3.1 Hotfix Handover Plan — Engineering + Security + Business

> 직전 클라우드 세션에서 작성·검증된 핸드오버. PC 세션에서 그대로 참조해 작업 진행.

## Context

v0.1.3에서 모든 LLM 호출이 `RouterWrapper`로 이관된 직후 발생한 회귀로
`core/reasoning_engine.py:920`의 `self.llm.get_cache_stats()` 호출이 500
에러를 발생시킴. 본 문서는 (1) 엔지니어링 핫픽스, (2) 보안 감사 결과,
(3) 사업화·신뢰도·bus factor 관점의 비즈니스 트랙을 통합해 PC 세션이
v0.1.3.1 → v0.2.0 → v0.3.0 흐름을 일관되게 다룰 수 있게 함.

## ⚠️ Origin & Environment Caveats (반드시 먼저 읽을 것)

본 핸드오버의 관찰 출처는 **PC가 아닌 별도 노트북**:
- 그 노트북에 GitHub에서 신규 clone → 신규 설치 → 가동 시점에 발생한 증상
- Ollama 모델 풀, 하드웨어 사양, `wiki/` 데이터셋 모두 PC와 다를 수 있음
- v0.1.2/v0.1.3 릴리스노트의 성능 수치(p50 25.7s → 23.8s 등)는 **노트북 전용** 측정. PC 재현 기대 금지

따라서 항목들을 **환경 독립** (소스코드 사실)과 **환경 의존** (런타임·데이터·하드웨어)으로 나눠 다룸.

### 환경 독립 (PC에서도 그대로 참)
- `core/reasoning_engine.py:920` 호출, `RouterWrapper` 메서드 부재, `core/gemma_client.py:89-100` 원본 구현
- `config.py:101` default `"gemma2:2b"` (릴리스노트는 `gemma4:e4b`라 기재)
- `llm/router.py:23` import, `llm/providers/deepseek_client.py` 안에 `QwenCoderClient`
- `frontend/static/chat.js:568-576` 가짜 setInterval, `server_llmwiki.py:542-644` 단일 JSON 반환
- 코드베이스 내 tool_use / ReAct 부재
- 🔒 보안 항목 A·B·C·D
- 💼 비즈니스 항목 B2(문서 정합성), B3(JEPA 네이밍) — 모두 코드 사실

### 환경 의존 (PC에서 별도 확인 필요)
- 500 에러 PC 재현 가능성, ollama 모델 가용성, `wiki/` 데이터셋 존재 여부, 하드웨어 성능

## PART 0 — PC Pre-flight Checks

```bash
ollama list
grep -E "^(JAMES_LLM_MODEL|JAMES_JWT_SECRET|JAMES_API_KEY)=" .env 2>/dev/null | cut -d= -f1
python server_llmwiki.py &
curl -s http://localhost:8000/admin/llm/cache_stats
ls wiki/entity/prod/ 2>/dev/null | head
```

## 검증 결과 요약

| # | 항목 | 결과 | 분류 |
|---|---|---|---|
| PART 1 | `reasoning_engine.py:920` RouterWrapper `get_cache_stats` 부재 | **CONFIRMED** | 환경 독립 |
| PART 2 ① | QwenCoder 라우팅 미사용 | **DISPUTED** — 이미 wiring | 환경 독립 |
| PART 2 ② | default `gemma2:2b` (릴리스노트는 `gemma4:e4b`) | **CONFIRMED 불일치** | 혼합 |
| PART 2 ③ | Tool use / ReAct 부재 | **CONFIRMED** | 환경 독립 |
| PART 2 ④ | 가짜 타이머, SSE 부재 | **CONFIRMED** | 환경 독립 |
| 보안 A | `core/auth.py` JWT_SECRET hardcoded fallback | **CONFIRMED** | 환경 독립 |
| 보안 B | `core/auth.py` 4개 기본 계정 평문 패스워드 | **CONFIRMED** | 환경 독립 |
| 보안 C | `config.py` API_KEY hardcoded fallback | **CONFIRMED** | 환경 독립 |
| 보안 D | JWT 8h vs SECURITY.md "24-hour" | **CONFIRMED 불일치** | 환경 독립 |
| 비즈 B2-1 | README hybrid `60+20+20` vs 코드 `60+20+10+10` | **CONFIRMED 불일치** | 환경 독립 |
| 비즈 B2-2 | `calculate_confidence` `50+20+20+10` (또 다른 가중치) | **CONFIRMED 추가 발견** | 환경 독립 |
| 비즈 B3 | `core/jepa_adapter.py` 실체는 keyword 동의어 사전 | **CONFIRMED** (docstring "LLM 호출 금지" 자명) | 환경 독립 |

## PART 1 — 500 에러 (코드 레벨)

호출 체인:
- `core/reasoning_engine.py:920` — `stats = self.llm.get_cache_stats()`
- `core/reasoning_engine.py:37-38` — `RouterWrapper("general")` 할당
- `llm/router.py` ~L250-278 — `get_cache_stats` 미정의
- `core/gemma_client.py:89-100` — 원본 구현

**옵션 A — passthrough 추가 (권장, v0.1.3.1)**:
```python
def get_cache_stats(self) -> dict:
    inner = self._get_llm()
    if hasattr(inner, "get_cache_stats"):
        return inner.get_cache_stats()
    return {"hits": 0, "misses": 0, "hit_rate_%": 0.0}
```

**옵션 B — 라우터 통합 캐시 통계**: #15와 합본, v0.2.0 후보.

## PART 2 — 로드맵 조정

- **①** `llm/providers/deepseek_client.py` → `qwen_coder_client.py` 리네임 + import 갱신
- **②** `config.py:101` default `gemma2:2b` → `gemma4:e4b`, `.env.example` 동기화 (PC `ollama pull gemma4:e4b` 선행)
- **③** Tool use / ReAct — v0.3.0+ epic
- **④** SSE 스트리밍 — `frontend/static/chat.js:568-576` 가짜 타이머 → EventSource, `server_llmwiki.py:542-644` `/query/` SSE화 (v0.2.0)

## 🔒 Security Audit (2026-05-06)

### Git 히스토리 잔존 (영구, 회복 불가)
v0.1.0 초기 커밋(`d15bf66`)에 Windows 사용자명·OneDrive 경로·BASE_DIR 하드코드·작성자 이메일 포함. `c71799a4`로 현재 코드는 정리됐으나 히스토리는 그대로. BFG/git-filter-repo는 모든 fork·PR·SHA 깨짐 → **비용 > 가치, 권장 안 함**.

### 비노출 확인
실제 시크릿 패턴(`tvly-*`, `sk-*`) 0건. `.env`는 `.gitignore` 등록. `.env.example`은 placeholder만.

### 코드 내 약한 디폴트 (즉시 패치 — 실제 fallback 문자열 본문 미인용)

| # | 위치 | 위험도 |
|---|---|---|
| **A** | `core/auth.py` ~L25-28 — `JWT_SECRET` env 미설정 시 하드코드 fallback으로 모든 JWT 서명 | **HIGH** |
| **B** | `core/auth.py` `_init_db()` defaults (~L70-78) — admin/manager1/employee1/guest 평문 패스워드가 소스에 박힘 | **CRITICAL** |
| **C** | `config.py` ~L179-181 — `JAMES_API_KEY` 미설정 시 fallback. WARNING 한 줄만 출력 | **MEDIUM** |
| **D** | `core/auth.py` L14 (`JWT_EXPIRE = 3600 * 8`) vs `SECURITY.md` "24-hour expiration" | **LOW** (문서 불일치) |

### GitHub Settings (5분, 무료)
공개 저장소는 secret scanning + push protection + Dependabot 보안 업데이트 모두 무료 활성 가능. `mcp__github__run_secret_scanning`이 GHAS(유료) 에러를 반환했으나 이는 MCP 도구의 GHAS 경로 호출 문제. Settings → Code security 웹 UI 직접 활성 필요.

### P0 패치 코드

```python
# A — JWT_SECRET fail-fast
JWT_SECRET = os.environ.get("JAMES_JWT_SECRET")
if not JWT_SECRET or len(JWT_SECRET) < 32:
    raise RuntimeError("JAMES_JWT_SECRET must be set (32+ chars)")

# B — 기본 계정 축소 + 랜덤 패스워드
import secrets as _secrets
_admin_pw = os.environ.get("JAMES_INIT_ADMIN_PW") or _secrets.token_urlsafe(16)
defaults = [("admin", _hash_password(_admin_pw), "admin")]
if not os.environ.get("JAMES_INIT_ADMIN_PW"):
    print(f"[AUTH] Initial admin password (CHANGE IMMEDIATELY): {_admin_pw}")

# C — API_KEY fail-fast
API_KEY = os.environ.get("JAMES_API_KEY")
if not API_KEY:
    raise RuntimeError("JAMES_API_KEY must be set")
```

D는 운영자 정책 결정 후 SECURITY.md 또는 코드 일치.

## 💼 Business / Commercialization Track (2026-05-06)

엔지니어링·보안 트랙과 별개 — 사업화·외부 신뢰도·bus factor 관점.

### B1 — 외부 벤치마크 통합 (RAGAS 또는 BEIR) ⭐ 영업 자료 1순위
- 현재: 자체 12쿼리 STEP 7 benchmark만. README "Real-data validation: Pending"
- 목표: 공인 벤치마크 1개 점수 → 영업·논문·블로그 객관 지표
- 추천: **RAGAS** (faithfulness / answer_relevance / context_precision / context_recall) — 4지표가 그대로 영업 자료 카드. 한국어 평가 지원도 충분
- 대안: BEIR 일부 (NQ, HotpotQA) — 학술 표준 강함, 영어 위주
- 작업: `scripts/benchmark/ragas_runner.py`, `reports/benchmark_v0.2.md`, README "Benchmarks" 섹션
- 비용: 1주, GPU/Ollama 가용 시점에서 시작

### B2 — API/문서 정확성 점검 (신뢰도 누수 차단)
외부 평가자가 처음 보는 순간 신뢰가 무너지는 작은 어긋남들.

| # | 위치 | 현상 | 정정 |
|---|---|---|---|
| 1 | `README.md` Architecture 다이어그램 "Hybrid Search" 줄 | "Vector(60%) + BM25(20%) + keyword(20%)" (3-way, 합 100%) | 실제 4-way: `core/retrieval_engine.py::hybrid_search` `0.6*v + 0.2*b + 0.1*k + 0.1*n`. README를 4-way로 정정 (vector 60% / BM25 20% / keyword 10% / **name 10%**) |
| 2 | `core/retrieval_engine.py::calculate_confidence` (파일 끝) | confidence 가중치 `0.5*v + 0.2*b + 0.2*kw_clip + 0.1*nm_clip` (합 ≠ 1.0, hybrid_search와 다름) | hybrid_search와 동일 의도면 통일, 다른 신뢰도 로직이면 docstring으로 차이 명시 |
| 3 | (보안 D 항목 — JWT 8h vs SECURITY.md 24h) | 보안 트랙에서 트래킹 |

작업: 한 PR로 묶어 `docs(consistency): align README/code/SECURITY claims`. 반나절.

### B3 — `core/jepa_adapter.py` 네이밍 정정 ⭐ 학술 정확성
- 코드 검증 결과: 모듈 docstring이 직접 "역할: query expansion only / ❌ LLM 호출 금지 / ❌ Graph 접근 금지" 명시
- 실체: `_SYNONYM_MAP` 딕셔너리 + 한국어 stopword 필터. embedding 0건, 예측 0건, joint architecture 0건
- JEPA(Joint-Embedding Predictive Architecture, Yann LeCun)와 메커니즘적으로 무관. 이름이 동작과 어긋나 학계·실무자 모두 오해 유발
- 정정: `core/jepa_adapter.py` → `core/query_expander.py` + 환경변수 `JEPA_TOKEN_HARD_LIMIT` → `QUERY_EXPAND_TOKEN_LIMIT`(호환성 deprecation 1 사이클)
- 영향 범위: `[JEPA]` print prefix, `jepa.expand_ok` 로그 step 이름, import 사이트 — 일괄 grep + sed
- 비용: 반나절, B2와 같은 PR 또는 별도 (별도 권장 — 리네임은 단독 PR이 리뷰 쉬움)

### B4 — 두 번째 컨트리뷰터 1명 확보 ⭐ Bus factor=1 해소 (사업화 전제)
- 현재: 단일 저자(Hashevolution). 모든 PR/이슈/릴리스 단독. **사업화 검토 시 첫 차단 사유**
- 목표: 외부 활성 컨트리뷰터 +1 (PR 머지 권한, code review 가능, 머지된 PR 2~3건)
- 접근:
  - `priority:medium` 이슈에 `good first issue` / `help wanted` 라벨 부여 (#2 CP949, #4 metadata fallback, #14 upload progress UI 적합)
  - README "Contributing" 섹션 강화 (현재 1줄) — 환경 셋업, 환영하는 PR 카테고리, 첫 PR 가이드
  - r/LocalLLaMA, r/MachineLearning, HackerNews "Show HN: Local Graph-RAG with security focus, looking for first external contributor" 노출 1회
- 측정: 외부인의 머지된 PR 1건 → bus factor 2
- 비용: 운영 3~6개월 누적, 즉시 시작

### B5 — 외부 red-team 1회 (정규식 가드 한계 객관화)
- 현재: 31+ injection 정규식 + instruction isolation. 자체 83-item 적대 테스트 100% pass. SECURITY.md가 "synthetic-data testing ≠ production security" 자체 명시
- 목표: 외부 red-team으로 다음 데이터 확보:
  - 정규식 우회 성공률 (예: 100건 중 N건 우회)
  - 우회 카테고리 (인코딩, 번역, role-play, 다중 턴, 등)
  - 결정 경계의 정성 분석
- 활용:
  - **데이터 있음 = 영업 시 정직한 답변**: "정규식 가드 X% 차단, 잔여 Y 카테고리는 ML 가드(LLaMA-Guard / ShieldGemma) 추가로 보강 중"
  - 데이터 없음 = "31+ pattern" 마케팅이 외부 검증 시 무너짐
- 비용:
  - 1차 셀프: `garak` / `promptfoo` 자동 도구 1주
  - 2차 인적: 보안 회사 또는 독립 연구자 컨설팅 1~2주
- 후속: red-team 결과 기반 ML 가드 도입 — `core/security_layer.py`에 LLaMA-Guard 또는 ShieldGemma 호출 통합
- 의존: 🔴 P0 보안 패치 선행 필수 (P0 미해결 상태로 외부 red-team은 의미 없는 점수만 양산)

### 비즈니스 트랙 우선순위 매트릭스

| 항목 | 사업화 가치 | 비용 | 의존성 | 권장 시기 |
|---|---|---|---|---|
| **B1** RAGAS 통합 | 매우 높음 (영업 #1) | 중 (1주) | 데이터셋 준비 | v0.2.0 직후 |
| **B2** 문서 정합성 | 중-높음 (신뢰도) | 낮음 (반나절) | — | v0.2.0 안에 |
| **B3** JEPA 리네임 | 중 (학술 정확성) | 낮음 (반나절) | import 사이트 추적 | v0.2.0 안에 |
| **B4** 컨트리뷰터 | 매우 높음 (사업화 전제) | 중 (3~6개월 운영) | 외부 채널 | 즉시 시작, v0.3 목표 |
| **B5** 외부 red-team | 매우 높음 (보안 마케팅) | 중-높음 (1~2주 + 컨설팅비) | 🔴 P0 보안 선행 | v0.2.0 직후 |

## 진행 순서 통합 (Engineering + Security + Business)

| # | 트랙 | 작업 | Release |
|---|---|---|---|
| 0 | Eng | PART 0 Pre-flight | — |
| 1 | Eng | **PART 1 옵션 A 핫픽스** | **v0.1.3.1** |
| 2 | Sec | 🔴 **P0 보안 A·B·C·D** | **v0.2.0** 첫 커밋 |
| 3 | Eng | PART 2 ② config default 정정 | v0.2.0 |
| 4 | Eng+Biz | PART 2 ① 파일 리네임 + **B3 JEPA 리네임** | v0.2.0 |
| 5 | Biz | **B2 문서 정합성** PR | v0.2.0 |
| 6 | Sec | 🟠 P1 GitHub Settings + git config noreply | 즉시 |
| 7 | Eng+Sec | 🟡 P2 pre-commit + SECURITY.md 보강 | v0.2.0 |
| 8 | Biz | **B1 RAGAS 통합** | v0.2.0 직후 |
| 9 | Sec+Biz | **B5 외부 red-team** (P0 후) | v0.2.0 직후 |
| 10 | Biz | **B4 컨트리뷰터 확보** (지속) | v0.3.0 목표 |
| 11 | Eng | PART 2 ④ SSE 스트리밍 | v0.2.0 후보 |
| 12 | Eng | PART 2 ③ Tool use / ReAct | v0.3.0+ |
| 13 | (선택) | PART 1 옵션 B (#15와 합본) | v0.2.0+ |

## Critical files

- `core/reasoning_engine.py` — L37-38, L920
- `llm/router.py` — RouterWrapper (~L250-278), L23
- `core/gemma_client.py` — L89-100
- `llm/providers/deepseek_client.py` — QwenCoderClient (B3 인접)
- `config.py` — L101 (`JAMES_LLM_MODEL`), ~L179-181 (`API_KEY` fallback)
- `core/auth.py` — L14, ~L25-28, `_init_db()` defaults (~L70-78)
- `core/retrieval_engine.py` — `hybrid_search` (~L60-80), `calculate_confidence` (파일 끝, B2-2)
- `core/jepa_adapter.py` — B3 리네임 대상 전체
- `frontend/static/chat.js` — L568-576
- `server_llmwiki.py` — L542-644
- `README.md` — Architecture 다이어그램 "Hybrid Search" 줄 (B2-1), Contributing 섹션 (B4)
- `SECURITY.md` — Authentication / Specific Caveats (D, 위생 가이드)

## Verification

PC 데이터셋·모델 가용성에 따라 단계별 분기.

- **PART 1 핫픽스**: 최소 `python -c "from llm.router import RouterWrapper; r=RouterWrapper('general'); print(r.get_cache_stats())"` 무 AttributeError. 데이터 있으면 `step7_query_test.py` 12쿼리 회귀
- **🔴 P0 보안**: 환경변수 미설정 → fail-fast. 설정 후 정상 기동 + 첫 부팅 admin 임시 패스워드 콘솔 출력
- **B1 RAGAS**: `scripts/benchmark/ragas_runner.py` 실행 후 `reports/benchmark_v0.2.md`에 4지표 점수 기록. README "Benchmarks" 섹션 추가 후 점수 인용
- **B2 문서 정합성**: README의 "Hybrid Search" 줄과 `core/retrieval_engine.py::hybrid_search` 가중치 grep 결과가 일치
- **B3 JEPA 리네임**: `grep -r "jepa\|JEPA" --include="*.py" .` 결과 0건 (deprecation alias 제외) + 기존 통합 테스트 통과
- **B5 red-team**: 결과 보고서를 `reports/redteam_v0.2.md`에 기록, SECURITY.md에 차단률·잔여 카테고리 갱신

## PC 세션 인계 사항

- 본 문서는 `claude/sync-sessions-across-devices-XO5In` 브랜치
- 시작:
  ```bash
  git fetch origin
  git checkout claude/sync-sessions-across-devices-XO5In
  git pull --ff-only origin claude/sync-sessions-across-devices-XO5In
  ```
- **🔴 P0 보안 패치는 v0.2.0 첫 커밋**으로 처리. fail-fast 도입 시 환경변수 필수 → PC에 `JAMES_JWT_SECRET`·`JAMES_API_KEY` set 후 진행
- v0.1.3.1 핫픽스 PR과 v0.2.0 P0 보안 PR은 **분리 권장** (호환성 깨짐 차이)
- B1·B4·B5는 단일 세션 작업이 아닌 **운영 트랙** — 핸드오버에 박제만 해두고 별도 GitHub 이슈로 epic 등록 권장

## 본 핸드오버 자체의 위생 점검

- 자격증명 / 토큰 / 실제 키값 — **미포함**
- 실제 사용자명·이메일·로컬 경로 — **미포함** (히스토리 잔존 사실만 일반론)
- 코드 내 하드코드 fallback 문자열 — **미인용** (file:line만, PC 세션 grep)
- 익스플로잇 단계 / 우회 PoC — **미포함** (벡터·카테고리만 일반론)
- 내부 IP / 비공개 호스트 — **미포함** (`localhost` / 표준 Ollama URL만)
- 컨설팅 회사명·외부 인사 명단 — **미포함** (B5 컨설팅 채널은 운영자 결정)

향후 핸드오버·릴리스노트·이슈·PR 본문 동일 원칙 유지.
