{
  "pass": false,
  "risks": [
    {
      "severity": "critical",
      "description": "supabase/functions/_shared/ai-provider.ts:129,185-188에서 Gemini 호출이 stream:false인 경우에도 :streamGenerateContent 엔드포인트를 사용한 뒤 단일 generateContent 응답 형태(data.candidates[0]...)로 파싱합니다. suggest-topics/index.ts:48-52가 stream:false로 호출하므로 result.content가 비거나 파싱 실패하여 'AI 응답 파싱 실패' 500이 날 가능성이 높습니다."
    },
    {
      "severity": "high",
      "description": "supabase/functions/suggest-topics/index.ts는 서버 측 인증, 플랜 검증, 토큰/사용량 차감 없이 고정 provider(gemini-2.0-flash-lite)를 직접 호출합니다. 프론트의 useFeatureGate는 UI 제한일 뿐이라 anon key를 가진 사용자가 Pro 전용 ai_topic_suggest를 우회 호출할 수 있습니다."
    },
    {
      "severity": "medium",
      "description": "suggest-topics/index.ts:32-34는 'suggest_topics 함수' 사용을 요구하지만 callAI/Gemini 호출에는 tool/function declaration이 없습니다. 모델이 함수 호출 형식이나 설명문으로 응답하면 현재 JSON/번호 목록 파서가 실패할 수 있습니다."
    },
    {
      "severity": "low",
      "description": "src/pages/Generate.tsx:101-123은 요청 시작 시 aiTopicsRequested=true로 바꾸고, 실패하거나 data.topics가 없을 때 빈 목록 상태를 유지합니다. 토스트는 보이지만 기존 빠른 주제 목록까지 사라져 실패 후 복구 UX가 나쁩니다."
    }
  ],
  "suggestions": [
    "Gemini 호출부에서 options.stream=false이면 :generateContent 엔드포인트를 사용하고, stream=true일 때만 :streamGenerateContent를 사용하도록 분기하세요. 스트리밍 응답과 비스트리밍 응답 파서도 별도로 검증하세요.",
    "suggest-topics 엣지 함수에서 Authorization JWT를 검증하고 user id 기준으로 ai_topic_suggest 권한, 플랜, 토큰/사용량을 서버에서 확인하세요. 가능하면 기존 callAIWithTokenCheck 같은 경로로 통합하세요.",
    "프롬프트를 실제 구현과 맞춰 'JSON만 반환'으로 바꾸거나, Gemini tool/function calling을 실제로 구성하세요. 예: {\"topics\":[\"...\"]} 스키마만 허용하도록 응답 형식을 강제하세요.",
    "프론트는 성공적으로 topics 배열을 받은 뒤에만 aiTopicsRequested=true로 바꾸거나, 실패 시 setAiTopicsRequested(false)로 되돌려 기본 quickTopics가 계속 보이게 하세요.",
    "엣지 함수 단위 테스트/스모크 테스트에 정상 JSON, 번호 목록, 빈 content, Gemini API 오류, 권한 없는 호출 케이스를 추가하세요."
  ],
  "source": "codex_companion",
  "fallback_reason": null,
  "error": null,
  "task_id": "task-2100",
  "timestamp": "2026-04-22T06:24:08.667330+00:00"
}