어제부터 9 commit 누적 (P1+P1.5+P2+STAGE 1+STAGE 2 풀). 마지막 한 commit 1 파일만
남아있음 — DiagnoseFlow2.tsx 본체에 v3 OTP 흐름 wiring.
이 commit이 박혀야 v3 anti-abuse 패러다임이 실제 작동.
92e76b0)7c94f84, main 대비 8 commit 앞섬)| 새 endpoint 7개 | 박힘 | 호출하는 사용자 진입점 X |
| AgentGateModal·OtpVerifyModal | 박힘 | JSX에 mount 안 됨 |
| migration v3 (otp/fraud/system_state) | Supabase 적용됨 | OTP row 진입 X |
app/diagnose/DiagnoseFlow2.tsx | wiring 0 | 여전히 STAGE 1 hotfix 흐름 (즉시 발송) |
PM 작업지시서 #3 가드레일 표현 실수:
"app/diagnose/* 격리 영역 절대 수정 금지 (예외: AgentGateModal·OtpVerifyModal만 추가 OK)"
→ 코덱스가 정확히 따라 본체 수정 0. 결과 모달은 박혔지만 dead.
의도는 "본체 임의 수정 금지, 단 모달 wiring은 OK"였어야 함.
코덱스가 자체 진단으로 정확히 짚음: "PM이 OTP 모달을 /diagnose 플로우에 연결하라고 판단하면 가드레일을 별도 해제해야 합니다." — 그 해제가 STAGE 2.5.
| 수정 | app/diagnose/DiagnoseFlow2.tsx | v3 OTP 흐름 wiring (이번 가드레일 해제 영역) |
/diagnose 라우트(영업 QR 진입점)는 app/diagnose/page.tsx → DiagnoseFlow2.tsx 사용 (page.tsx에 명시적 import)./keeper-out/diagnose는 별개 — DiagnoseFlow.tsx 사용. STAGE 1 hotfix 시 frontend 변경이 여기 박혔지만 메인 라우트와 무관 (backend route.ts는 공유).// 현재 (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
// 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;
}
URL ?ref={code}는 모달 외부에서 처리. 모달은 사용자 직접 입력만 받음.
?ref 값은 buildPayload 또는 funnel_event payload에 tracking 정보로 박는 게 맞음.
또는 향후 모달 props 확장. CC 자체 판단.
자동님이 새 CC 세션 열고 아래 박스 통째 복사 → 첫 메시지로 붙여넣기. 박스가 self-contained — 보고서 본문 참조 없이도 작업 가능.
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 + 보고. 임의 해결 절대 금지.
git show <sha>로 실제 변경 확인app/diagnose/DiagnoseFlow2.tsx 청록 0 hit