CC 인계 작업지시서 — STAGE 2.5 wiring

2026-05-06 · 코덱스 일일 리밋 → Claude Code 새 세션 · 1 commit, 1 파일 본체 변경 · ultrathink로 정밀 작성

🔄 코덱스 → Claude Code 인계

어제부터 9 commit 누적 (P1+P1.5+P2+STAGE 1+STAGE 2 풀). 마지막 한 commit 1 파일만 남아있음 — DiagnoseFlow2.tsx 본체에 v3 OTP 흐름 wiring. 이 commit이 박혀야 v3 anti-abuse 패러다임이 실제 작동.

01.현재 정확한 상태 (ultrathink 검증)

Production (main HEAD = 92e76b0)

92e76b0Merge staging: keeper-out STAGE 1 abuse hotfixmain HEAD
8920553fix: /diagnose requires naver_place_id + defers payout failures
2683490fix: hotfix migration for abuse gates
4ab6eb4Merge staging: keeper-out admin lead detail P1.5+P2

Staging (HEAD = 7c94f84, main 대비 8 commit 앞섬)

7c94f84cron — recheck-completion + escalate-fallbackSTAGE 2
b62c5abLeadDetailClient sections 14·15·16·17STAGE 2
3768a7aALL HOLD toggle + system_state CRUDSTAGE 2
51230b9fallback queue page (TM 6h escalate)STAGE 2
df00a7adispatch + reject API + balance preflight + capSTAGE 2
4cb032fpending queue page + TM call APISTAGE 2
ceabe96request-otp + verify endpoints + AgentGateModal + OtpVerifyModalSTAGE 2
bf9a8ecmigration v3 — anti_abuse tablesSTAGE 2

Dead code 상태 (STAGE 2.5에서 fix)

새 endpoint 7개박힘호출하는 사용자 진입점 X
AgentGateModal·OtpVerifyModal박힘JSX에 mount 안 됨
migration v3 (otp/fraud/system_state)Supabase 적용됨OTP row 진입 X
app/diagnose/DiagnoseFlow2.tsxwiring 0여전히 STAGE 1 hotfix 흐름 (즉시 발송)

02.왜 wiring이 미완으로 끝났나

PM 작업지시서 #3 가드레일 표현 실수: "app/diagnose/* 격리 영역 절대 수정 금지 (예외: AgentGateModal·OtpVerifyModal만 추가 OK)" → 코덱스가 정확히 따라 본체 수정 0. 결과 모달은 박혔지만 dead. 의도는 "본체 임의 수정 금지, 단 모달 wiring은 OK"였어야 함.

코덱스가 자체 진단으로 정확히 짚음: "PM이 OTP 모달을 /diagnose 플로우에 연결하라고 판단하면 가드레일을 별도 해제해야 합니다." — 그 해제가 STAGE 2.5.

03.이번 작업 = 1 commit 1 파일

수정 app/diagnose/DiagnoseFlow2.tsx v3 OTP 흐름 wiring (이번 가드레일 해제 영역)
★ 컴포넌트 path 정확

04.흐름 변경 5단계

// 현재 (line ~172):
async function submit() {
  setStep("submitting");
  const photo_urls = await uploadPhotos();
  const res = await fetch("/api/keeper-out/diagnose", { ... });   // STAGE 1 hotfix 흐름
  setStep(data.payback_token ? "reveal" : "done_basic");
}

// v3 흐름 (교체):
[A] URL ?ref={code} 추출 (useSearchParams)
[B] state 추가: showAgentModal, showOtpModal, otpSessionId, otpExpiresAt, otpError, agentName, agentCode
[C] submit() → setShowAgentModal(true)만
[D] onAgentSubmit(name, code):
    photo upload → fetch("/api/keeper-out/diagnose/request-otp", { payload, agent_name, agent_code })
    → otpSessionId, expires_at 받아 setShowOtpModal(true)
[E] onOtpSubmit(otpCode):
    fetch("/api/keeper-out/diagnose/verify", { otp_session_id, otp_code, payload, agent_code })
    → 성공 시 setStep("review_pending") // 새 step
[F] "review_pending" 안내 화면 — "본사 콜센터 곧 연락 / 24h 내 발송"
[G] JSX에 · mount

05.모달 props 시그니처 (이미 박힌 것)

// app/diagnose/components/AgentGateModal.tsx
interface AgentGateModalProps {
  open: boolean;
  busy?: boolean;
  error?: string | null;
  onClose: () => void;
  onSubmit: (agentName: string, agentCode: string) => void;
}

// app/diagnose/components/OtpVerifyModal.tsx
interface OtpVerifyModalProps {
  open: boolean;
  busy?: boolean;
  error?: string | null;
  expiresAt?: string | null;   // 5분 카운트다운용 ISO timestamp
  onClose: () => void;
  onSubmit: (otpCode: string) => void;
}
⚠️ AgentGateModal에 prefillCode prop 없음

URL ?ref={code}는 모달 외부에서 처리. 모달은 사용자 직접 입력만 받음. ?ref 값은 buildPayload 또는 funnel_event payload에 tracking 정보로 박는 게 맞음. 또는 향후 모달 props 확장. CC 자체 판단.

06.★ Claude Code 전달 박스 (복붙용)

자동님이 새 CC 세션 열고 아래 박스 통째 복사 → 첫 메시지로 붙여넣기. 박스가 self-contained — 보고서 본문 참조 없이도 작업 가능.

📋 CLAUDE CODE 전달 박스 — 통째 복사
ultrathink. 이 작업은 정밀해야 한다. 1 commit 1 파일 본체 수정.

[ROLE]
당신은 새 Claude Code 세션. PM(별도 세션, Opus 4.7 1M)이 이 작업지시서를 작성.
이전 작업자(Codex)가 일일 사용 리밋 도달 → 당신이 인계.
직전까지 9 commit 누적, 마지막 한 commit 1 파일만 남아있음.

[환경]
- 디렉토리: /home/tlswkehd/projects/keeper-direct (이미 cd 가정)
- 브랜치: staging (현재 HEAD = 7c94f84)
- production main HEAD: 92e76b0 (P1+P1.5+P2+STAGE 1 hotfix 머지된 상태)
- staging은 main 대비 8 commit 앞섬 (STAGE 2 v3 anti-abuse) — main 머지 X
- AGENTS.md / .claude/CLAUDE.md / docs/keeper-out/anti-abuse/06-p3-implementation-brief.md
  자동 로드 또는 시작 시 한 번 읽기 권장

[직전 작업자(Codex) STAGE 2 회신 verbatim 핵심]
- 8 commit 모두 staging에 박힘 (bf9a8ec ~ 7c94f84)
- 새 endpoint 7개 + 모달 2개 + 페이지 2개 + 섹션 4개 + cron 2개 모두 동작
- tsc / eslint / build 모두 PASS
- 색깔 룰 / 텍스트 룰 grep 0 hit
- Vercel preview 7 URL 200
- T+20~24h 정책: (c) 하이브리드 채택
- approved_by FK 안전 처리 (UUID 있을 때만)
- 코덱스가 직접 짚은 critical:
  "PM이 OTP 모달을 실제 /diagnose 플로우에 연결하라고 판단하면,
   app/diagnose/DiagnoseFlow2.tsx 수정 금지 가드레일을 별도 해제해야 합니다"
  → 그 해제가 이번 STAGE 2.5. 본체 수정 OK.

[Dead code 현재 상태]
- /api/keeper-out/diagnose/request-otp + /verify endpoints: 박혀있지만 호출 X
- AgentGateModal.tsx + OtpVerifyModal.tsx: 박혀있지만 JSX mount X
- 사용자가 진단 폼 끝에서 [지원금 받기] 누르면 여전히 STAGE 1 hotfix 흐름
  (즉시 발송 + 잔액≥5만원 + day cap 200 게이트)
- main 머지하면 admin/pending 큐 빈 채로 라이브 → 의미 zero

[이번 작업 = STAGE 2.5 wiring (1 commit, ~1-2시간)]
파일: app/diagnose/DiagnoseFlow2.tsx (메인 영업 QR 진입점)

이 컴포넌트가 정확한 대상인지 확인:
  cat app/diagnose/page.tsx | head -3
  → "import DiagnoseFlow2 from "./DiagnoseFlow2";" 확인

흐름 변경 (5단계):
A. URL ?ref={code} 추출
   - useSearchParams() 사용
   - const ref = searchParams.get("ref")
   - buildPayload() 또는 funnel_event payload tracking에 활용
   - (모달 prefill prop 없으므로 UI 노출은 텍스트로만 — 예: "영업원 코드 자동 인식: 86Af9")

B. state 추가:
   - showAgentModal: boolean (default false)
   - showOtpModal: boolean (default false)
   - otpSessionId: string | null
   - otpExpiresAt: string | null
   - otpError: string | null
   - agentName: string (모달 결과 보관)
   - agentCode: string (모달 결과 보관)
   - submitBusy: boolean

C. submit() 함수 (line 172) 변경:
   기존 fetch("/api/keeper-out/diagnose") 호출 제거.
   대신 setShowAgentModal(true)만.

D. 새 함수 onAgentSubmit(agentName, agentCode):
   1. setShowAgentModal(false), setSubmitBusy(true), setStep("submitting")
   2. const photo_urls = await uploadPhotos();
   3. const res = await fetch("/api/keeper-out/diagnose/request-otp", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          payload: { ...buildPayload(), photo_urls },
          agent_name: agentName,
          agent_code: agentCode,
        }),
      });
   4. const data = await res.json();
   5. if (res.ok) {
        setOtpSessionId(data.otp_session_id);
        setOtpExpiresAt(data.expires_at);
        setSubmitBusy(false);
        setShowOtpModal(true);
      } else {
        setOtpError(data.error || "request-otp 실패");
        setSubmitBusy(false);
        setShowAgentModal(true);  // 다시 표시
      }

E. 새 함수 onOtpSubmit(otpCode):
   1. setSubmitBusy(true);
   2. const res = await fetch("/api/keeper-out/diagnose/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          otp_session_id: otpSessionId,
          otp_code: otpCode,
          payload: buildPayload(),
          agent_code: agentCode,
        }),
      });
   3. const data = await res.json();
   4. if (res.ok) {
        setShowOtpModal(false);
        setSubmitBusy(false);
        setStep("review_pending");
      } else {
        setOtpError(data.error || "OTP 검증 실패");
        setSubmitBusy(false);  // 모달 그대로
      }

F. 새 step "review_pending" 추가:
   - step 타입 union에 "review_pending" 추가
   - 렌더 (기존 reveal/done_basic 패턴 차용):
     ```
     ✅ 진단 완료!

     📞 본사 콜센터에서 곧 연락드릴 예정이에요
     💸 1만원 N페이 (입력하신 010-XXXX-XXXX)
        본사 확인 콜 종료 후 24시간 내 발송됩니다

     🔒 보안을 위해 자동 즉시 발송이 아닌 본사 확인 후
        발송하는 정책으로 운영하고 있어요
     ```
     (카피는 docs/keeper-out/anti-abuse/05-human-approval-queue.md §7과 일치)

G. JSX에 모달 mount (return 안 어디든 적절한 위치):
    setShowAgentModal(false)}
     onSubmit={onAgentSubmit}
   />
    setShowOtpModal(false)}
     onSubmit={onOtpSubmit}
   />

[모달 props 시그니처 — 이미 박혀있음, 변경 X]
AgentGateModal: { open, busy?, error?, onClose, onSubmit:(name,code)=>void }
OtpVerifyModal: { open, busy?, error?, expiresAt?, onClose, onSubmit:(otpCode)=>void }

[request-otp / verify 응답 형식 확인]
시작 전 grep으로 정확한 필드명 확인:
  grep -A 5 "NextResponse.json" app/api/keeper-out/diagnose/request-otp/route.ts
  grep -A 5 "NextResponse.json" app/api/keeper-out/diagnose/verify/route.ts
필드명 mismatch 시 그쪽 응답 형식에 맞춰 frontend 수정 (route.ts는 변경 X).

[가드레일 — 이번 라운드]

✓ 허용:
  - app/diagnose/DiagnoseFlow2.tsx 본체 수정 OK (이번 핵심)
  - 새 step "review_pending" 추가 OK
  - state 추가 OK

✗ 금지:
  - 청록 #009090 / #00b4b4 / #006B6B / #007575 / #e6f7f7 사용 X
  - "한화" / "키퍼" / "하나비전" 사용자 face 텍스트 X
  - /api endpoints 수정 X (이미 박힌 endpoint 그대로 호출만)
  - app/diagnose/components/AgentGateModal.tsx · OtpVerifyModal.tsx 수정 최소화
    (props 시그니처 그대로 사용. 만약 mismatch 시 코드를 frontend가 맞춰)
  - next.config / package.json 수정 X
  - main 직접 push X (staging만)
  - --no-verify / --force / --no-ff X
  - untracked 13개 보존 (CLAUDE.md / lib/keeper-out/catchrank.ts /
    app/api/keeper-out/catchrank/sync-places/ / app/landing/ / context/ / data/ /
    docs/2026-trend-report-recon/ / public/assets/ / scripts/ ...)
    — git status로 시작 시 확인. 이미 dirty/untracked 상태 그대로 유지.

[검증 절차 — 각 단계 self-check]

1. npx tsc --noEmit → exit 0
2. npx eslint app/diagnose/DiagnoseFlow2.tsx → 0 hit
3. npm run build → success (라우트 빌드 확인)
4. (가능하면) dev 서버 띄우고 실제 흐름 테스트:
   npm run dev → http://localhost:3000/diagnose 접속
   search → q1 → q2 → pain → q3 → q4 → contact → photo → [지원금 받기]
   ① AgentGateModal 등장 → agent_name + agent_code 입력 → confirm
   ② "submitting" → request-otp 호출 → 응답 (otp_session_id, expires_at)
   ③ OtpVerifyModal 등장 (5분 카운트다운) → SMS OTP 6자리 입력 → confirm
   ④ verify 응답 → "review_pending" 안내 화면 등장
   ⑤ Supabase MCP 또는 SQL로 row 진입 확인:
      SELECT id, store_name, payout_status, created_at
      FROM keeper_outbound_leads ORDER BY created_at DESC LIMIT 1;
      → payout_status='pending_review'

[리스크 안전망 — CC 신중함 활용]

- staging push 전 1차 confirm 가능 (자동님께 "staging push 진행하시겠습니까?" 한 줄)
- SMS 발송이 dev 환경에서 어려움 (BALERION_API_KEY 미설정 등):
  → 자동님께 보고. 강제 진행 X.
- 모달 props 시그니처 mismatch:
  → frontend가 맞춰. 모달 내부 수정 최소화.
- 충돌/장애/예상 외 상태:
  → 즉시 stop + 자동님께 보고 (자동님이 PM에게 전달)
- main 머지는 절대 X (PM 검증 후 자동님 결정)

[커밋]
파일 1개:
  app/diagnose/DiagnoseFlow2.tsx
모달 props 시그니처 mismatch 시 모달 컴포넌트도 수정 가능.

커밋 메시지:
  feat(keeper-out/diagnose): v3 OTP 흐름 wiring (AgentGate + OtpVerify 모달 호출 + review_pending step)

staging push:
  git push origin staging

[보고 형식 — PM에게 회신할 형식 (자동님이 전달)]

## STAGE 2.5 wiring 결과 보고

### 1. commit sha (1개)
-  feat(keeper-out/diagnose): v3 OTP 흐름 wiring ...

### 2. /diagnose 흐름 변경 요약
- 기존: search → ... → photo → submit → /api/keeper-out/diagnose POST 즉시 발송
- 신: search → ... → photo → [지원금 받기] → AgentGate → request-otp → OtpVerify → verify → review_pending

### 3. 검증 결과
- npx tsc --noEmit: PASS / FAIL (출력 그대로)
- npx eslint: PASS / FAIL
- npm run build: PASS / FAIL (실패 시 마지막 30줄)
- dev 실제 흐름 테스트: 5단계 모두 통과 / 어디서 막혔나
- Supabase row 진입 확인: 확인됨 / 못함 (이유)

### 4. URL ?ref={code} 동작
- 테스트한 ref (있으면)
- buildPayload 또는 tracking에 어떻게 활용

### 5. SMS OTP 검증 방법
- 실제 SMS 받음 / mock 사용 / dev 환경 막힘 (이유)

### 6. 결정 사항 (자체 판단한 것)
- 모달 props mismatch 처리 / 새 step 추가 위치 / 그 외

### 7. 막힌 곳 / 의문 (있으면)

### 8. 다음 후속 후보 (있으면)

[참조]
- in-repo P3 풀 스펙: docs/keeper-out/anti-abuse/06-p3-implementation-brief.md
- in-repo 휴먼 승인 큐: docs/keeper-out/anti-abuse/05-human-approval-queue.md
- 이 작업지시서 풀 버전 (PM 보고서):
  https://ai.shinjadong.cloud/2026-05-06-cc-handoff-stage25-wiring.html
- v3 anti-abuse 풀 설계:
  https://ai.shinjadong.cloud/2026-05-05-keeper-out-anti-abuse-design-v3.html
- 직전 PM 검증 (STAGE 2 PASS):
  https://ai.shinjadong.cloud/2026-05-06-pm-review-stage2-pass-stage25-wiring.html

[시작]
지금 시작.
1. git status / git log -3 로 현재 상태 자체 확인
2. 위 검증으로 컴포넌트 path 정확한지 확인 (cat app/diagnose/page.tsx)
3. request-otp / verify 응답 필드명 grep으로 확인
4. 흐름 변경 [A]~[G] 박기
5. tsc/eslint/build self-check
6. dev 서버 띄울지 결정 (자동님께 confirm 가능)
7. staging push (push 전 자동님께 confirm 가능 — 신중)
8. 위 보고 형식으로 PM(자동님 통해)에게 회신

충돌/장애/의문 시 즉시 stop + 보고. 임의 해결 절대 금지.

07.CC 보고 받은 뒤 PM 처리 흐름

  1. CC 회신 8 섹션 (커밋 sha + 흐름 변경 + 검증 + ref + SMS 검증 + 결정 + 막힌 곳 + 후속) 받기
  2. PM 검증:
    • git show <sha>로 실제 변경 확인
    • 색깔 룰 grep — app/diagnose/DiagnoseFlow2.tsx 청록 0 hit
    • 흐름 5단계 정합성 (코드에 [A]~[G] 모두 박혔는지)
    • 모달 props 매칭 (mismatch 0건이어야)
  3. 이상 시 fix 디스패치 / 정상 시 자동님 main 머지 결정 보고서
  4. main 머지 후 production v3 패러다임 라이브 → TM 콜센터 운영 시작 + 자동님 fallback 큐 모니터링

참조 흐름 — 오늘 누적 (자동님 폰에서 검토용)

  1. v3 anti-abuse 풀 설계 — 자동님 5/6 승인
  2. PM 컨텍스트 동기화 — 5/6 흡수
  3. Codex Brief #3 — STAGE 1+2 작업지시서
  4. PM 리뷰 #2 — STAGE 2 PASS, wiring 미완 발견
  5. 이 문서 — CC 인계 STAGE 2.5 wiring