title: 펑크폴 리팩토링 마스터플랜 (A→B 신규 구축) — 단계별 계획 + 단계별 측정모델
type: 계획
date: 2026-07-03
priority: P0
projects: [punkpoll-v2]
services: [아키텍처, 리팩토링, 측정모델, 마이그레이션]
purpose: peter 지시 — A(현재 prod) 무접촉 유지, B(리팩토링본) 별도 구축. 치밀한 단계별 마스터플랜 + 각 단계 측정모델. 완성 후 jegwon 논의 → dev 실행.
유형: 계획 (jegwon 논의 대상 — 논의·확정 후 dev 실행)
저자: manager · 2026-07-03 · 근거: Phase 0 as-built 6건(전건 게이트 PASS)·계약 C-01·결정로그 D-001~D-043
원칙: A(현재 버전)는 검증·전환 완료 전까지 무접촉. B(리팩토링본)는 별도 사본으로 신규 작성(기존 코드 수정·복사 없이 as-built 를 명세로만 사용).
1. A 무접촉 — B 구축·검증·전환 전 과정에서 A의 코드/DB/트래픽 변경 없음.
2. B는 신규 작성 — as-built 명세를 근거로 재작성. 기존 코드 붙여넣기 금지.
3. 모든 단계는 측정모델로 완료 판정. 측정 미달 = 다음 단계 진입 금지(보수적 게이트).
4. 이중 독립검증 — dev 구현 + manager 게이트(별도 경로 재현). 불확실 = 거부.
5. 인간 게이트 — prod 전환·실채널 연결은 jegwon/오너 승인.
6. 모든 소통은 refactor-chat 중심.
7. A 기능 무수정 원칙(jegwon 2026-07-07 재천명) — 우리는 A의 현재 구조·기능을 B로 리팩토링하는 것이지 A 기능을 수정하는 것이 아니다. 기능·UX·구조 = A 그대로. 보안 강화조차 UX/기능이 바뀌면 구현 전 jegwon 상정 필수(사례: 지갑 '연결' UX divergence).
8. 최종 목적 = 다른 AI 개발자가 B 코드를 손쉽게 구현/재사용 — A의 불필요 코드(부채)는 미이식(제외 근거 기록), 살아있는 기능은 전부 이식. 코드 기준: 도메인 분리·계약 명시·테스트 동반·A소스 ref 주석.
punkpoll/punkpoll-v2 (GitHub org punkpoll, private — bigcloud0403 접근 org. 계획상 punkpoll-dev 였으나 실제 접근 org 로 확정, 2026-07-03 생성 완료). 브랜치: main(안정)·dev(개발). A 저장소와 git 히스토리 완전 분리(유출 이력 미상속)./Users/bigcloud/project/claudecode/<repo> (현행 유지)./Users/bigcloud/project/punkpoll-v2/ (신규, 별도 클론). 작업/분석용 클론과도 분리.
punkpoll-v2/
├─ apps/
│ ├─ web/ # Next.js 14 — BFF(구 punkpoll_web 경계) + wallet(non-custodial) 모듈
│ ├─ core-api/ # NestJS — 비즈니스·인증(JWT)·결제(KCP)·콜백 수신·상태머신
│ ├─ chain-worker/ # NestJS — TX 파이프라인·nonce 단일화·토큰/FT·IPFS·온체인 이벤트 검증
│ └─ messenger-gateway/ # NestJS — 카카오/텔레그램·OAuth·nullifier 발급·알림톡(NCP)
├─ packages/
│ ├─ contracts/ # zod 계약 + 계약 테스트(C-01~C-12) — as-built ground-truth 고정
│ ├─ crypto/ # nullifier v2·HMAC 서명·암복호 (키는 전부 env/KMS, 소스 평문 0)
│ ├─ db/ # punkpoll_v2_* 스키마·마이그레이션·리포지토리
│ ├─ events/ # BullMQ 이벤트 스키마·큐 어댑터(콜백 단일화)
│ ├─ config/ # env 스키마(zod)·설정 로더 (M1: 시크릿 소스 0 강제)
│ └─ shared/ # 공통 타입·유틸·에러코드
├─ infra/ # PM2 ecosystem·.env 템플릿(주입식)·배포 스크립트
├─ test/ # E2E(시뮬레이터 연계)·통합·부하·카오스
└─ tooling/ # 마이그레이션 대사(reconcile)·sanitize·A/B 동등성 비교
| 자원 | A (현행) | B (신규) |
|---|---|---|
| 서버계정 | ubuntu | punkpoll-v2 (신규 리눅스 유저, 격리) |
| 프로세스 | PM2(ubuntu) | PM2(punkpoll-v2), 포트 별도 대역(예: 9600~) |
| Redis | 기존 :6379 | :6380 별도 인스턴스 |
| DB | 기존 platform/mina | **로컬 MySQL 신규 스키마 punkpoll_v2_*** |
| 시크릿 | 현행(일부 유출) | 전량 신규 발급·env/KMS 주입, 소스 평문 0 |
| 도메인 | 현행 | v2 전용 서브도메인(예: v2.punkpoll.com) — 검증용, 실채널 미연결 |
| 블록체인 | Avalanche mainnet | Fuji testnet 또는 dry-run(온체인 미제출) 강제 — Phase 6 전환 시점에만 mainnet 스위치 |
| 결제(KCP) | 실결제 | sandbox(site_cd T0000) 강제 — 실결제 방지 |
전환(Phase 6) 전까지 B는 실사용자/실채널과 미연결. A와 트래픽 격리.
★B 체인 격리 (dev 역제안 반영, 안전 필수)★: B의 chain-worker 가 실채널 미연결이어도 Avalanche mainnet 에 TX 제출 시 실자산 이중 민팅·이중 리워드·실가스 발생(A가 이미 mainnet 사용 중). 따라서 B chain-worker 는 환경변수로 체인 분리(as-built #3 txSender 에 Fuji/Mainnet RPC 공존 확인) — Phase 1~5 전 구간 Fuji/dry-run 강제, mainnet TX 0건(→ M11 감사). mainnet 스위치는 Phase 6 cutover 에서만.
각 Step은 [목표] [산출물] [측정모델(지표·목표·측정방법·게이트)] [의존성] [리스크] 로 구성. 측정모델 게이트를 통과해야 다음 Step 진입.
A의 현재 동작을 증거로 고정한 단계. 이미 완료분은 B 설계의 입력.
| Step | 목표 | 산출물 | 상태 |
|---|---|---|---|
| 0.1 | as-built 명세(6 저장소군) | as-built/*.md | ✅ 완료(D-009~028) |
| 0.2 | C-01 콜백 계약 16종 | contracts/callbacks/ | ✅ 완료(D-041/042) |
| 0.3 | 나머지 계약(C-02~C-12) ground-truth | contracts/ | ⏳ 진행 |
측정모델(Phase 0): as-built 저장소 커버리지 100%(6/6 ✅) · 계약 그룹 12개 중 ground-truth 문서화 비율(현재 C-01 완료). 게이트: manager 이중검증 PASS.
빈 모노레포 + 인프라 + 계약 스위트 실행환경. "코드는 아직 없지만 뼈대와 검증장치가 선다."
Step 1.1 — 모노레포 스캐폴드 + 인프라 셋업
infra/ PM2 ecosystem, CI(빌드·lint·test) 파이프라인.pnpm build 전체 성공(0 에러) ②CI 그린 ③4 app 헬스체크 200(빈 상태) ④Redis:6380 ping·MySQL punkpoll_v2 접속 OK. 목표: 전부 통과. 측정방법: CI 로그·헬스 스크립트. 게이트: manager 재현.Step 1.2 — packages/config·crypto·db 기반
packages/config·packages/crypto·packages/db.Step 1.3 — 계약 스위트 완성·실행 (C-01~C-12)
packages/contracts/ 전 그룹 + fixtures/sanitize/.Step 1.4 — ★관측성 기반(계측·구조화 로그·알림)★ (D-051): A는 모니터링이 약해 split-brain 미지급을 오래 못 잡음. B는 처음부터 메트릭·구조화 로그·트레이싱·알림을 내장 → 무결성 위반(미지급→성공기록 등)·에러·지연을 즉시 표면화.
Phase 1 완료 판정(측정모델 종합): 빌드·CI 그린 / 시크릿 0 / 계약 100% 통과 / 인프라 헬스 OK / 관측성·알림 동작. 미달 시 Phase 2 진입 금지.
파이프라인 한 경로를 처음부터 끝까지 B로 실제 동작. 아키텍처 리스크를 조기 검증.
Step 2.1 — 슬라이스 선정·구현: 설문 생성 → 배포(core-api→chain-worker) → 온체인(★Fuji testnet 강제★) → 결과 콜백 확정(→core-api) → 상태 반영. BullMQ 이벤트 큐·HMAC·nonce 단일화·온체인 이벤트 검증 포함. (결제 경로 포함 시 KCP sandbox T0000.)
★fee payer 관리 — 참여 우선 QoS 풀(D-052)★: A는 유형별(배포/참여/토큰) 전용 fee payer + 2계층 메모리 nonce(재시작 리셋·drift·6월 정체). B: chain-worker 단일 계층 + 공유 fee payer 풀 + QoS(참여 최우선). ①nonce 단일 소스·영속(Redis/DB)·원자 할당·온체인 자가치유(getTransactionCount 대조) ②공유 풀(유형 무관 최한가 계정 분배, 가동률↑) ③참여(투표/join) 전용 예약 용량·우선순위 레인 — 배포/토큰 버스트가 참여를 굶기지 않음 ④계정별 nonce 격리(막힌 TX=그 계정만) ⑤유형 태깅(정산·관측) ⑥master→slave AVAX 자동 top-up+잔액 알림. 측정: 부하 시 참여 TX 우선 지연<목표·참여 starvation 0·nonce 재시작 유실/중복 0(M5).
Phase 2 완료 판정: 슬라이스 E2E 100% + 무결성/멱등/인증 축 통과. 아키텍처 확정.
슬라이스에서 검증된 골격 위에 A의 전 기능을 도메인별로 재구축. 각 도메인은 계약을 무회귀 만족.
| Step | 도메인 | as-built 근거 | 측정모델(핵심 지표) |
|---|---|---|---|
| 3.1 | 인증(JWT·OAuth·nullifier 발급) + ★단일 신원 서비스·통합 회원등록★ | #1·#2·#5 | 로그인/refresh 계약 100%, nullifier 발급 결정성, first-touch 등록 멱등 |
| 3.2 | 결제(KCP 주문·승인·취소·잔액·PUNK) | #1 | 결제 계약 100%, 금액 정합 대사 0 오차 |
| 3.1b | ★PII 암호화(주민번호·전화번호)★ (D-049) | #1 | 저장 평문 0·알림톡 발송 시에만 복호화·복호 경로 인가 통제 |
| 3.3 | 참여/투표(joinSurvey→IPFS→온체인→콜백) + ★설문 생명주기·중단/일시정지·리워드 에스크로★ | #2·#4·#5 | 이중리워드 0, 무결성 축 통과, 생명주기 상태전이·반환 정합 |
| 3.4 | 토큰/FT/SBT/IPFS | #3·#4 | 배포·민팅 계약 100%, IPFS 멱등, ★즉시 CID 발급 + 지연 Pinata 핀★(아래 주) |
| 3.5 | 챗봇(카카오 4채널·텔레그램·알림톡) | #5 | 카카오 응답 p99<4.5s, 채널 config화(복제 0) |
| 3.6 | 지갑(non-custodial, ★WebView surface + 플랫폼 백엔드 공유★) | #6 | 개인키 서버 전송 0, 서명 왕복 정상, WebView 임베드 동작, 플랫폼 신원 공유 |
| 3.7 | web BFF·관리자·통계 | #1·#6 | BFF 21경로 계약 100% |
측정모델(Phase 3 각 Step 공통): ①해당 도메인 계약 테스트 100% 통과 ②A 대비 회귀 0(골든샘플·대사) ③보안 불변식(시크릿 0·인증 필수·fail-closed) ④성능 목표치(도메인별) ⑤체인 격리 준수(Fuji/dry-run, mainnet TX 0=M11)·결제 sandbox. 게이트: manager 도메인별 이중검증.
★통합 회원 등록·단일 신원 서비스 — jegwon 요구(D-047)★: A는 회원 등록 권한이 챗봇 signupService 에 있어 '챗봇 먼저 써야 회원' 잔재(플랫폼 로그인→needs_signup / attemptDirectSignup 도 챗봇 호출). B: core-api 단일 신원 서비스가 회원+nullifier 생성 권한 보유, 플랫폼 OAuth·챗봇 auth 둘 다 이 하나를 호출. findOrCreateMember(provider, 외부ID) 멱등 → 진입점(플랫폼/챗봇)·순서·동시성 무관 first-touch 가 회원+nullifier 원자적 생성, 챗봇 의존 제거 → 어디서 처음 쓰든 즉시 회원·챗봇 정상. 범위: 같은 provider 자동통합 포함 / 교차 provider(카카오↔텔레그램 한 사람) 링크는 기반만(신원모델 Canonical User(1)←ProviderIdentity(N) 스키마로 미래 확장 가능하게 설계, 링크 로직·전화 매칭 UI 는 이번 제외).
★설문 생명주기·중단/일시정지·리워드 에스크로 — jegwon 요구(D-046)★: A엔 배포 중단이 없음. B는 설문 생명주기 상태머신으로 제공: DRAFT→QUEUED→SUBMITTING→CONFIRMING→ACTIVE→(PAUSED↔ACTIVE)→STOPPED/CANCELLED. 규칙: ①단계별 중단 안내 ②수수료: 온체인 전송 前 취소=가스 0·리워드 전액반환 / 전송 중·후 취소=이미 쓴 가스 차감·나머지 리워드 즉시 반환 ③등록 완료 후 '중지': 참여 차단 + 미지급 리워드 즉시 반환 ④참여 일시중지/재개: 온체인 Pausable(트러스트리스) 권장. 리워드 에스크로 필수(배포 시 재원 예치→참여마다 지급→중지/취소 시 잔액 반환). ★반환액 = 에스크로 총액 − 참여자에게 지급해야 할 금액(기지급 + 참여했으나 미지급 owed 둘 다 예약)★ = 참여자 권리 보호 후 잉여만 반환. 반환도 온체인 TX라 큐·무결성 검증 대상(미반환→반환됨 기록 0). 측정: 상태전이 정합·수수료 계산 정확·반환액=에스크로−(지급+owed) 정확·반환 무결성.
★pin 서버(IPFS) 설계 원칙 — jegwon 정정 반영(D-045)★: pin 서버의 주역할 = 대량 참여 시 Pinata rate limit 우회용 '즉시 CID 발급' (영속성/GC 방지는 부차). B 는 3.4 에서 ① 참여 시 CID 즉시 발급(임계경로, Pinata 대기 없음) → 파이프라인 즉시 진행 ② Pinata 핀 업로드는 BullMQ(:6380) 큐로 순차·rate-limit·재시도 후처리 ③ CID 기준 멱등(A의 중복 핀 작업 갭 해소) = '즉시 CID + 지연 핀' 분리 패턴. 측정: 대량 참여 부하에서 CID 발급 지연<목표·Pinata 큐 순차 소진·중복 핀 0.
Phase 3 완료 판정: 전 도메인 계약 100% + 회귀 0 + 보안 불변식 충족.
A의 nullifier(공유 평문키 기반)와 B의 신규 nullifier 체계 간 유권자 신원 연속성 보존.
Step 4.1 — 호환 계층 설계·구현: 구키 복호 → 신키 재암호 매핑, 온체인 영구 호환(과거 발급 nullifier 무효화 금지).
Step 4.2 — ★split-brain 오염 데이터 온체인 소급 대사★ (D-050): A의 구버전 워커가 '미지급을 성공으로 기록'한 과거 오염 데이터는 A 중지만으론 안 사라짐. 마이그레이션 시 A의 '성공' 레코드를 온체인 이벤트와 대조 → 불일치(가짜 성공=실제 미지급) 교정/플래그 → B는 검증된 데이터로 시작.
Phase 4 완료 판정: 연속성 100% 대사 통과.
B를 A와 나란히 돌리되 실채널 미연결. 시뮬레이터/미러로 A와 동등성·성능 검증.
Step 5.1 — 섀도 실행·동등성 검증: sim.punkpoll.com 시뮬레이터를 B 대상으로 확장, A vs B 출력 동등성 비교(tooling/동등성).
Phase 5 완료 판정: 동등성·성능·안정성 목표 달성 → 전환 준비 완료.
검증된 B로 실제 전환. 오너가 채널 연결, A 중지. 롤백 경로 상시 확보.
Step 6.1 — 단계적 전환: DB 최종 이관·정합 대사 → 채널/도메인 전환(오너) → 소규모 카나리 → 전량 → A 중지(관찰기간 후).
Phase 6 완료 판정: 전환 안정 + 롤백 준비 확인 + A 안전 중지.
| ID | 지표 | 목표 | 측정 방법 | 연계 Phase |
|---|---|---|---|---|
| M1 | 시크릿 소스 평문 | 0건 | gitleaks/CI 전 파일 스캔 | 1~ |
| M2 | 계약 테스트 통과율 | 100% | packages/contracts CI | 1·3 |
| M3 | 파이프라인 E2E 통과율 | 100% | 시뮬레이터 시나리오 | 2·3 |
| M4 | TX 무결성(미지급→성공기록) | 0건 | chain-worker 테스트+온체인 대사 | 2·3 |
| M5 | nonce 안전(재시작 중 유실/중복) | 0건 | 카오스 테스트 | 2 |
| M6 | 카카오 응답 p99 | <4.5s | gateway 계측 | 3 |
| M7 | nullifier 연속성 | 100% | 마이그레이션 대사 | 4 |
| M8 | A/B 출력 동등성 | 100%(오차 정의) | 동등성 비교 tooling | 5 |
| M9 | A 무접촉 | 변경 0 | A repo/서버 감사 | 1~6 |
| M10 | 전환 안정성·롤백 | 목표·리허설 성공 | 실트래픽·롤백 리허설 | 6 |
| M11 | B mainnet TX (전환 전) | 0건 | chain-worker RPC 감사·온체인 조회 | 1~5 (dev 역제안) |
| M12 | PII(주민번호·전화번호) 저장 평문 | 0건 | DB 스캔·복호 경로 감사 | 3 (D-049) |
| M13 | split-brain 오염 소급 대사 | '성공' 레코드 온체인 대조 100%·가짜성공 교정 | 온체인 대사 스크립트 | 4 (D-050) |
| M14 | 관측성·무결성 알림 | 위반 주입 시 알림 발화 | 알림 재현 | 1~ (D-051) |
1. A/B 분리 설계(§1) — 특히 같은 EC2 병행(D-002) vs 별도 인스턴스, v2 서브도메인.
2. 디렉토리/모노레포 구조(§1.3) 확정 여부.
3. 단계 순서·측정모델 목표치(§2·§3) — 특히 무결성(M4)·동등성(M8) 허용 오차, 전환(Phase6) 조건, B 체인 격리(Fuji/dry-run, M11 mainnet TX 0)·결제 sandbox 확정 (dev 역제안).
4. nullifier 마이그레이션(Phase 4) 우선순위·리스크 수용.
5. 일정·자원(섀도 운영 EC2 여력).
다음: 이 마스터플랜을 refactor-chat 에 공유 → jegwon 논의 → 확정분으로 dev 실행(Phase 1.1부터). 확정 전 prod 무접촉·구현 착수 없음.
진단: 계획 루프·사람-AI 분업·승인 게이트는 최신 방법론과 이미 일치. 최대 갭 = 검증 자동화 — 치명 트랩 5건(env 미배선·stale-dist·십진/wei·toFixed 절삭·merge-drop)이 전부 유닛 green 뒤에 숨었고 reviewer 수동 렌즈만 잡음(검증 사람 병목).
| 우선순위 | 항목 | 내용 | 시점 |
|---|---|---|---|
| P0 | PR 위험등급+롤백 1줄 | 모든 PR 본문에 Low/Med/High + 롤백 커밋범위 명시. 배포 공지에도 롤백 1줄 | 즉시 발효 |
| P0 | 대형건 1페이지 계획서 | 남은 대형건(커스텀토큰·M4 컷오버)만 전후·단계·롤백 계획서 | 즉시 |
| P1 | 자동 A/B parity diff | 핵심 공개 엔드포인트 A(prodPool)↔B(v2) 응답 골든 diff 하니스(CI). 2층 구조: 자동 diff=1차 그물(값 어긋남 대량 조기포착) / reviewer=2차 의미론 렌즈(의도된 divergence 판정·정수검산·온체인 바이트대조·인과규명) — 대체 아닌 증강 | T5 실증 후 착수(backend1+reviewer 설계) |
| P1 | e2e 머지게이트 승격 | 컴포지션 e2e(풀루프 불변식)를 PR 머지게이트로 | 〃 |
| P2 | codemod 롱테일 | 푸터/정적 22페이지 + 명확 패턴 반복 일괄 변환(커버리지 가속). 코어(온체인·도메인)는 제외 — 사람승인 루프 유지 | P1 후 |
| P3 | 측정 보강 | ①OPTIMIZATION_REPORT A대비B 정량표(LOC·복잡도) ②커버리지 실측→코어 80% 게이트 결정 ③로그인 게이트 페이지 인증 픽스처 골든 캡처 | 순차 |
하지 않을 것: 전면 자동 리팩토링(사람 게이트 제거)·DL 스멜탐지 별도 도입(자동 A-diff가 대체)·코어 도메인 codemod.