# [긴급/프로덕션 hotfix] InsuRo 정보성키워드 생성 502 — load_dotenv 순서 버그 수정

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "server/main.py"
    - "server/tests/**"
  forbidden_paths:
    - ".env"
    - ".env.*"
    - ".github/**"
  commands:
    - "pytest"
    - "python3 -m py_compile"
    - "python3 -c"
  merge_policy: "tiered"
  ttl_hours: 24
```


## 배경 (회장 긴급 장애: 정보성키워드 30개 추천 전면 실패)
프로덕션(api.insuro.biz)에서 `POST /api/insuro/infokeyword/generate` 가 502 Bad Gateway.
서버 로그 원문:
```
infokeyword /generate worker 4xx [401]: Client error '401 Unauthorized' for url 'http://127.0.0.1:8100/generate'
body={"detail":"Invalid or missing API key"}
POST /api/insuro/infokeyword/generate HTTP/1.1" 502 Bad Gateway
```

## 근본 원인 (ANU 정밀 진단 — 키 불일치 아님, 코드 순서 버그)
파일: `/home/jay/projects/InsuRo/server/main.py`

- **41~46행**에서 모듈 로드 시점에 httpx 클라이언트를 생성하며 인증 헤더를 즉시 평가:
  ```python
  _infokeyword_http_client = http_client.AsyncClient(
      base_url="http://127.0.0.1:8100",
      timeout=60.0,
      headers={"X-API-Key": os.getenv("INFORKEYWORD_API_KEY", "")},
  )
  ```
- 그런데 **53~54행**에서야 `.env`를 로드:
  ```python
  from dotenv import load_dotenv
  load_dotenv(dotenv_path=Path(__file__).resolve().parents[1] / ".env")
  ```
- 결과: 클라이언트 생성 시점엔 `INFORKEYWORD_API_KEY`가 아직 환경에 없어 **빈 문자열("")이 헤더에 박제**됨 → worker(8100) 인증 실패(401) → 502.

### 검증된 사실 (재확인 불필요, 그대로 신뢰)
- 정답 키는 `/home/jay/workspace/.env.keys` 의 `INFORKEYWORD_API_KEY` (21자). main 루트 `.env`(`/home/jay/projects/InsuRo/.env`)·worker 실제 프로세스 environ·`.env.keys` **3곳 모두 동일**(sha256 앞8자 `f08957b8`).
- worker(`/home/jay/projects/InfoKeyword/worker/config.py:51`)는 `API_KEY = os.getenv("INFORKEYWORD_API_KEY","")`, `.env.keys`(override) 최우선 로드. 정답키로 직접 호출 시 인증 통과(작업 수행 타임아웃, 401 아님) 확인됨.
- worker의 `/home/jay/projects/InfoKeyword/.env` 파일(43자)은 **미사용**(override로 덮임). 건드리지 말 것.

## 목표 (Surgical — 최소 수정)
`main.py`에서 **`load_dotenv(...)` 호출을 `_infokeyword_http_client` 생성(41~46행)보다 위로 이동**하여, 클라이언트 헤더가 실제 키를 갖도록 한다.
- 가장 단순: 파일 상단 import 직후(그리고 `os` import 이후, httpx 클라이언트 생성 이전)에서 `load_dotenv(dotenv_path=... / ".env")`를 1회 실행하도록 순서를 바로잡는다.
- 주의: `.env.keys` override도 필요하면 함께 로드(worker와 동일 정합). 단 main이 기존에 `.env`만 로드했다면 그 동작 유지 + 순서만 교정하는 것을 우선(불필요한 동작 변경 금지). `.env` 하나만으로도 21자 키가 들어오므로 순서 교정만으로 해결됨.
- `_infokeyword_http_client` 외에 `os.getenv(...)`를 module-load 시점에 평가하는 다른 클라이언트/상수(예: `_naver_http_client` 인근)도 같은 함정이 없는지 점검하되, **이번 hotfix 범위는 infokeyword 502 해소로 한정**. 광범위 리팩터 금지.

## 검증 기준 (반드시 통과)
1. 로컬/서버에서 `python3 -c "import main"` 또는 서버 기동 시 `_infokeyword_http_client._headers`(또는 동등)에 21자 키가 실제로 들어감을 확인.
2. 서버 재기동 후 실제 엔드포인트:
   ```
   POST https://api.insuro.biz/api/insuro/infokeyword/generate  (인증된 관리자 토큰)
   → 200 OK, 정보성 키워드 목록 반환 (401/502 아님)
   ```
   최소한 서버 로컬에서 `curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:8001/api/insuro/infokeyword/generate ...` 가 더 이상 502가 아님을 확인.
3. 회귀: 기존 pytest(server/tests) 통과, 다른 엔드포인트 무영향.

## 배포/재기동 주의 (프로덕션 라이브)
- **라이브 서버 실체**: PID 3320093, `cwd=/home/jay/projects/InsuRo/server`, `uvicorn main:app --port 8001 --host 0.0.0.0` (Aug7 00:32 기동, 수동 setsid 프로세스). api.insuro.biz(cloudflared 터널)가 여기로 감.
- **systemd `insuro-api.service` 는 포트8001 충돌로 크래시 재시작 루프(5000회+) 중** — 실서비스는 위 수동 프로세스가 담당. 재기동 시 이 이중구조 유의(무턱대고 systemd start 하면 충돌). 재기동 절차는 현행 수동 프로세스를 graceful 교체하는 방식으로, **순단 최소화 + 재기동 직후 `/api/status` 200 확인** 필수.
- 커밋/PR: 기존 InsuRo 워크플로우(squash, ANU 독립검증 후 머지) 준수. hotfix이므로 브랜치 → PR → CI GREEN → ANU 검증 → 머지 → 재배포. 회귀 0 확인.
- **보고 필수 8항목**(완료 시): 수정 파일/라인 diff · import 순서 교정 내용 · 로컬 import 검증 결과 · 서버 재기동 방식 · `/infokeyword/generate` 실제 응답코드 · `/api/status` 200 여부 · 회귀 테스트 결과 · PR/머지 상태.

## 파일 경로 요약
- 수정: `/home/jay/projects/InsuRo/server/main.py` (41~54행 순서 교정)
- 참고(수정금지): worker `/home/jay/projects/InfoKeyword/worker/config.py`, `/home/jay/workspace/.env.keys`

## goal_assertions (auto-generated)
- `python3 -c "import main"`
- `curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:8001/api/insuro/infokeyword/generate ...`
