prod 영향 점검 중 발견. 직전 커밋(377389f)의 sticky 만료 회전은 **브라우저가 열려 있을 때만**
동작했다 — `_ctx is None` 이면 만료 검사를 건너뛰었다. 그런데 negodata 연동은 수동 트리거
전용이라 검색이 드문드문 들어오고, 그때는 유휴 정리(120s)로 브라우저가 닫힌 채 매 검색이
그 경로로 들어온다. 즉 **실사용 패턴에서만 안 먹는** 반쪽 수정이었다(377389f 커밋 메시지·README
의 '고쳤다'는 서술이 부정확했다).
실측(합성 게이트웨이·10포트, sticky 매번 경과):
연속 검색 IP 6개 순환 ✅
저트래픽 · 예산 3 IP 2개 예산이 대신 회전시켜 가려져 있었음
저트래픽 · 예산 0 **IP 1개 고정** ❌ ([DecodoConfig] 주석의 '0=시간창 회전만'이 거짓)
원인은 시계가 둘이었던 것이다. sticky 만료를 브라우저 기동 시각(_launched_at)으로 쟀는데,
브라우저는 닫혔다 열릴 때마다 시계가 되감긴다. IP 를 쥔 시간과 어긋나는 이 구조가 F1(예산
미발화)과 F4(회전 안 됨)의 공통 원인이었다.
→ 시계를 _session_started_at 하나로 통일하고 _launched_at 을 제거했다. 만료 판정은 브라우저가
닫혀 있어도 수행한다. 세 시나리오 모두 정상 회전 확인.
**prod 템플릿 kr_host 누락**(기존 문제, 이번 변경과 무관):
config.prod.toml.example 에 kr_host 가 없어 그대로 복사하면 네이버가 국가 무지정 게이트웨이로
떨어진다. 해외 residential IP 는 '비정상적인 접근'(2.6KB) 하드차단이고 회전으로 회복 불가라
네이버 결과가 통째로 0건이 된다. kr_host + naver_ip_request_budget 을 경고 주석과 함께 추가.
테스트 2건 추가(저트래픽 sticky 만료 회전 / IP 를 쥔 시간이 수명 내면 회전 안 함), 전체 253 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>