🏠 홈으로

캐시쉐이크 교훈 → 멍집사 적용 목록

형제 앱 캐시쉐이크가 v1.0.0 → v1.0.8 을 지나오며 실제로 겪은 사고·봉합·QA 를 전수로 훑어, 멍집사가 같은 값을 치르지 않고 가져올 것만 뽑았음. 총 건. 각 항목은 캐시쉐이크에서 무슨 일이 있었는지멍집사에서 무엇을 해야 하는지 순으로 씀.
멍집사 v1.0.3 까지 이미 반영된 38건은 중복이라 이 문서에서 뺐음 — 대조는 했고 목록만 안 실었음.

레포 phantomz-Inc/shake-n-earn-react-native (367 커밋 전수) Alvin 브랜치 작업 로그 8회차 캐시쉐이크 QA 시트 128건 멍집사 v1.0.3 라이브 코드 대조 · 기반영 38건 제외

[결론]

멍집사는 v1.0.3 까지 오면서 캐시쉐이크 교훈 38건을 이미 흡수했음 — 가장 비쌌던 3종(광고 완주 오탐 · 인스턴스 수명 · 늦은 정산 재폴링)이 전부 여기 들어감. 그 34건은 중복이라 뺐고, 이 문서에는 아직 안 한 85건만 남겼음. 남은 것은 "터졌을 때 원인을 알 수 있게 만드는 층"좁은 화면·문구·운영 도구 쪽에 몰려 있음.

캐시쉐이크가 가장 크게 손해 본 패턴은 버그 그 자체가 아니라, 앱이 원인을 이미 알고 있었는데 화면에도 서버에도 남기지 않아 진단을 AdMob 필률 역산으로 해야 했던 것임(260813). 멍집사도 지금 정확히 같은 상태임 — 광고 실패는 전부 한 문장으로 뭉개짐.

🎯 원씽 — 광고·보상 실패에 원인 분류 + 오류 코드를 붙이고 그 1건을 서버에 남길 것. 이거 하나면 앞으로 들어올 "광고가 안 나와요" 문의가 캡처 없이 즉시 판별됨. 캐시쉐이크는 이걸 8개월 뒤에야 했고, 그 사이 원인 분해에 하루를 통째로 썼음.
🔴 미적용 · 규율 — 멍집사에 아직 없거나, 습관으로 지켜야 할 것 🟡 부분 — 있으나 구멍이 있음 🟣 아이디어 — 캐시쉐이크도 아직 못 한 것

우선순위 Top 10 — 여기부터 하면 됨

전체 목록에서 비용 대비 효과가 큰 순. 1~4번은 각각 하루 안에 끝나는 크기임.

#할 것왜 지금크기항목
1광고 실패 원인 분류 + 오류 코드 노출지금은 전부 'failed' 한 덩어리 — 문의가 와도 원인을 못 가름. 캐시쉐이크는 이걸 안 해서 진단에 하루를 썼음S · JS만A1·A2
2app.jsonplatforms: ["ios","android"] 추가없으면 eas update --platform all 이 web 번들에서 깨져 OTA 발행 자체가 중단XS · 1줄E1
3어드민 minAppVersion일반 밸런싱 폼에서 분리지금은 자유 입력 칸 — 9.9.9 오타 하나로 전 유저가 앱에 못 들어감SH4
4로그인 실패 원인별 안내(카카오 이메일 미인증 등)멍집사도 카카오 OAuth — 캐시쉐이크는 같은 유저가 하룻밤에 19회 재시도했음. "잠시 후 다시" 로는 영원히 안 됨SC2·C3
5resources?.food ?? 0상태 분리로 교체조회가 null 이면 보유분이 있어도 0 으로 확정 표시됨. 같은 파일의 새로고침 경로는 이미 ?? current 라 규칙이 어긋나 있음SB1
6정산 폴링에 error 상태 분리 + 벽시계 데드라인지금은 조회 실패가 고정 6회 시도를 갉아먹어 거짓 '지연' 안내가 뜸. 폴 1회당 타임아웃도 없음SA4
7소진 상태 문구를 "다 썼음 + 언제 돌아옴" 으로캐시쉐이크 260813 조사 결론 — "광고 안 나온다" 신고 대부분이 결함이 아니라 문구 문제였음. JS 전용이라 제일 쌈XSK4
8한글 어절 단위 줄바꿈 + 320dp 스윕 + 당겨서 새로고침플립·SE 에서 단어가 음절로 꺾임. RefreshControl 은 앱 전체에 0개라 유저가 할 수 있는 게 앱 재시작뿐임MF1~F4·F6·B4
9어드민 공지 팝업 + 광고 킬 스위치 + 설정 실시간 반영장애 때 알릴 채널이 푸시뿐이고, 광고를 끌 방법이 없음. 셋은 세트로 묶어야 값을 함M · 서버+클라H1·A7·H3
10탈퇴 → 재가입 어뷰징 가드지금은 탈퇴 후 재가입으로 사료 3 · 장난감 3 · 에너지 100 + 입양 기본 지급을 무한 반복 수령 가능. 광고 없이 자원이 생기는 유일한 경로임M · 서버I3

그다음 줄 — A5(SSV custom_data 형태를 테스트로 고정) · I1(뽑기 화면 확률·규칙 안내) · H10(cron 마지막 실행 시각 노출). 셋 다 반경이 작고 독립적임.

A. 광고 · 보상 무결성 (9)

캐시쉐이크가 가장 많은 피를 흘린 영역임. 리워드앱의 사고는 대부분 "광고는 봤는데 보상이 없다" 한 문장으로 들어옴.

A1광고 실패 원인을 앱이 알고도 버리고 있었음🔴 미적용

캐시쉐이크 SDK 는 에러 코드를 정확히 넘겼고 앱도 {code,message} 를 받아 들고 있었는데, 표시 조건이 extra.isProduction !== true 로 잠겨 실유저 빌드에선 절대 안 찍혔음. 서버 전송도 0건. 그래서 "광고가 안 나온다" 제보를 AdMob 필률 역산으로 진단해야 했음.

멍집사 광고 실패는 전부 'failed' 하나로 뭉개지고, 화면 문구도 원인 불문 단일임. 이 목록의 🎯 원씽.

A2원인별 안내 3분류 — network / no_fill / internal🔴 미적용

캐시쉐이크 adErrorGuidance 신설(260813) — 유저가 취할 행동이 달라지는 단위로만 갈랐음. 네트워크 계열이면 "Wi-Fi 끄고 데이터로 / 광고 차단 앱·VPN 끄기" 를 지시하고, 문의 대응용 오류 코드 1줄을 실유저 빌드에서도 남김. iOS·향후 버전이 접두사를 붙여도 흡수되게 부분 문자열 매칭으로 짰음.

멍집사 뽑기·황금상자·제작 3경로가 같은 문구를 씀. 분류 모듈 1개 + 문구 3종이면 끝남.

A3광고 실패 1건을 서버에 남길 것🔴 미적용

캐시쉐이크 260813 봉합 후에도 "코드가 화면에만 남는" 상태라, 유저가 캡처를 보내야 원인이 확정됨 — 남은 과제로 명시해 뒀음.

멍집사 ad_events 옆에 실패 로그 테이블 1개(또는 Sentry breadcrumb + 태그)면 다음부터는 캡처 없이 판별 가능함. Sentry 는 이미 붙어 있으니 태그로 먼저 해도 됨(비용 0).

A4정산 폴링이 '조회 실패' 와 '아직 안 옴' 을 구분해야 함🟡 부분

캐시쉐이크 서버는 결백이었음(SSV 최대 8.1초, 13초 창 초과 0/100). 원인은 클라 폴링이 네트워크 blip·토큰 갱신을 '미도착'과 동일 취급해 창을 소진한 것. 광고 액티비티 복귀 직후 Android 네트워크 재수립 구간에 몰려 터졌음. 봉합은 found/absent/error 3상태 + 시도 횟수가 아니라 벽시계 데드라인 + 폴 1회당 타임아웃.

멍집사 waitForAdSettlement 는 실패를 catch 로 삼키고 고정 6회 배열을 그대로 소진함 — error 버스트가 오면 캐시쉐이크와 같은 거짓 '지연' 이 뜸. 폴 1회당 타임아웃도 없어 한 번 행 걸리면 그 시도가 통째로 날아감.

A5SSV custom_data 가 길면 AdMob 이 콜백을 아예 안 보냄🔴 가드 없음

캐시쉐이크 응모 보상이 통째로 안 들어왔음. DB 로 확인해 보니 짧은 'shake'·'attendance' 는 SSV row 가 생기는데 'raffle_entry:<36자 uuid>' (49자)는 row 가 0건이었음 — 콜백 자체가 안 온 것. 봉합은 custom_data 를 짧게 되돌리고 광고 전 intent 레코드로 대상 아이템을 전달하는 구조(마이그 046).

멍집사 현재 JSON.stringify({userId, context}) ≈ 70자로 캐시쉐이크가 죽었던 길이보다 김. 지금 도착하고 있으므로 실동작은 통과 중이지만, 여기에 필드를 하나 더 붙이는 순간 조용히 죽을 수 있음. 지금의 형태에서 벗어나지 않는다를 단위 테스트로 잠가 둘 것 — 길이만이 아니라 콜론·특수문자·URL 인코딩까지 포함해서(실패 원인이 길이가 아니었을 가능성이 남아 있음).

A6보상 판정을 OTA 로 사라질 수 있는 값에 걸지 말 것🟡 확인 필요

캐시쉐이크 실/테스트 광고 단위 선택이 Constants.expoConfig.extra.isProduction 에 걸려 있었는데, eas updateeas.json 의 build env 가 아니라 EAS 대시보드 env 를 읽음. 거기에 APP_VARIANT 가 없어서 주말 운영 OTA 가 isProduction=false 로 구워졌고, 테스트 광고 단위는 SSV 를 안 쏘므로 실유저 전원의 보상이 조용히 멈췄음. 봉합은 "OTA 로 사라질 수 없는 신호"(실광고 env 존재 + 릴리스 빌드)로 이전.

멍집사 app-variant.ts 가 같은 extra.appVariant 를 봄. 광고 단위 ID 자체EXPO_PUBLIC_ADMOB_* env 직접 참조라 안전하지만, isDevApp 이 광고 표시 여부를 가르므로 대시보드 env 에 APP_VARIANT 가 실제로 있는지 1회 확인할 것.

A7광고 킬 스위치 — 장애 시 광고를 서버에서 끌 수 있어야 함🔴 미적용

캐시쉐이크 service_notice.ad_block_enabled 로 광고를 전면 차단하고 팝업 문구를 띄우는 경로를 만들어 뒀음(QA No.52). 실제 광고 장애 때 이걸로 버텼음.

멍집사 광고가 잘못 나가도 앱 배포 없이는 끌 수 없음. game_config 에 불리언 1개 + ad-gate 분기 1개면 됨 — 이미 있는 게이트 한 곳에만 붙이면 되므로 반경이 작음.

A8쿨타임을 바꿀 땐 서버 먼저, 클라 나중 — 그리고 불변식🟡 좁은 창 확인

캐시쉐이크 쿨타임 20→10초 축소 시 서버 게이트를 운영에 선적용한 뒤 클라를 내보냈음(순서 뒤집으면 서버가 cooldown 으로 거절). 동시에 폴링 스큐 버퍼 15→5초로 낮춰 "버퍼 < 쿨타임" 불변식(직전 보상 row 오매칭 방지)을 유지했음. 근거는 7일 4,390건 실측(p99 2.16초).

멍집사 같은 불변식이 이미 깨져 있음 — 항목별 쿨타임은 기획 30초인데 ad-settlement.tsCLOCK_SKEW_MS60초임. 다만 발화 조건이 좁음: 평소엔 lastEventId 로 판정하므로 무해하고, 기준점 조회가 실패해(baseline.unknown) 시각 비교로 떨어질 때만 직전 보상을 이번 것으로 오인할 수 있음. 스큐 값을 쿨타임보다 작게 두거나, unknown 경로에서는 아예 pending 으로 보수 판정할 것.

A9배너 위치가 너무 아래면 노출이 안 나옴🟣 아이디어

캐시쉐이크 QA No.101 — 홈 배너가 화면 하단이라 노출 자체가 안 생겨 수익이 안 났다는 지적. 다른 탭은 상단인데 정작 가장 중요한 홈만 하단이었음. 보류 상태로 미해결.

멍집사 배너를 넣을 때 첫 화면에서 스크롤 없이 보이는 위치부터 후보로 둘 것. 리워드 광고와 카니벌라이즈되지 않는 자리인지도 같이 봐야 함.

B. 상태 표시의 정직성 — "값이 0" 과 "값을 모름" (8)

캐시쉐이크에서 가장 유저 신뢰를 깎은 사고들이 전부 이 한 줄로 요약됨: 모르는 값을 아는 척 그렸다.

B1미확정 잔고를 "0" 으로 그리면 안 됨🔴 실제로 있음

캐시쉐이크 11,330원을 가진 실유저가 지갑에서 "0 포인트 / 오늘 0/15" 를 봤음. 초기 상태가 balanceWon:0 인데 캐시 미스 + 서버 동기화 실패가 겹치면 그 0 이 확정값처럼 남았음. 30일 실측으로 160명 중 29명이 겪었음. 봉합은 BalanceStatus(idle/loading/stale/ready/error) 신설 — 숫자 대신 "불러오는 중… / 다시 시도" 표시, 미확정 잔액으로 "잔액 부족" 단정 금지.

멍집사 use-query.ts 는 로딩·미로그인·오류를 4상태로 잘 갈라 놨는데, 사료·장난감은 그 훅을 안 거침. App.tsx 최초 로드가 food: resources?.food ?? 0 로 떨어뜨림 — getResources() 가 null 을 돌려주면 보유분이 있어도 0 으로 확정 표시됨. 같은 파일의 새로고침 경로는 ?? current.food(직전 값 유지)라 두 경로가 서로 다른 규칙을 씀 — 그 자체가 냄새임. 게다가 로그인 직후 잠깐 데모 수치가 그려짐(setCare(DEMO_CARE)) — use-query 가 막으려던 바로 그 사고임.

B2같은 결함 클래스는 화면 단위가 아니라 전수로 잡을 것🔴 규율

캐시쉐이크 거짓 0 을 봉합한 바로 다음 커밋fix: 거짓 0 게이팅 누락 3곳 마저 봉합 이었음 — 1차에서 지갑 탭 위주로 배선하다 홈·보상 카드 3곳을 빠뜨렸음. 같은 클래스인데 절반만 고친 것.

멍집사 이런 수정은 반드시 심볼 grep 전수 → 목록화 → 전부 순으로 갈 것. 파급영향 점검(R0)의 1단계가 정확히 이거임.

B3흡수 상태(absorbing state) — 갱신 경로가 하나면 영구 오작동이 됨🔴 점검

캐시쉐이크 황금알 무한흔들기 사고의 본질임. 재동기화 경로가 applyResolvedReward 하나뿐이고 그마저 조건부라, 그 경로가 끊기면 앱 재시작 전까지 영구. 프로덕션 20일에서 무보상 SSV 55건 중 47건이 이 경로, 영향 유저 약 17명.

멍집사 상태를 바꾸는 경로가 1개뿐이고 조건부인 곳을 목록화할 것 — 특히 재고(황금상자)·쿨타임·일 한도. 규칙: 갱신 경로는 최소 2개(주 경로 + 자가 회복).

B4자가 회복 경로 — focus / 포그라운드 복귀에서 재시도🔴 미적용

캐시쉐이크 retryBalanceIfNeeded() 를 홈 focus·포그라운드 복귀에 걸어 자력 회복시켰음. 이게 없으면 "화면에 앉아 있는 유저"는 영원히 못 벗어남.

멍집사 useAuthedQueryretry 를 넘겨주지만 자동 재시도 트리거가 없음 — 유저가 버튼을 눌러야 함. AppState active 복귀 + 화면 focus 를 재조회 트리거로 붙일 것.

B5백오프 재시도는 부팅 임계경로에서 빼야 함🔴 규율

캐시쉐이크 재시도를 넣으려다 계산해 보니 최악 5+0.5+5+1+5 = 16.5초 로 부팅 타임아웃(7초)을 넘겨 실패모드가 되레 악화됨. 그래서 재시도는 "호출자가 명시할 때만" 켜지고 기본값 0회, 부팅은 재시도 없음을 테스트로 고정했음.

멍집사 부팅 시 game_config·세션·프로필 조회가 스플래시를 막음. 여기에 재시도를 붙일 땐 반드시 총 상한부터 계산할 것.

B6낙관 갱신 vs 늦게 도착한 응답 — 순서 토큰이 필요함🔴 점검

캐시쉐이크 in-flight 응답이 로컬 갱신을 덮어써서 값이 되돌아가는 사고가 반복됐음. 소유권(generation) 검증만 있고 순서 검증이 없으면 재발한다고 규칙에 못 박아 뒀음.

멍집사 광고 후 잔고를 낙관 갱신하는 지점이 있으면 요청 순번을 달아 늦게 온 스냅샷이 새 값을 덮지 않게 할 것.

B7불변식으로 묶인 필드를 한쪽만 갱신하지 말 것🔴 규율

캐시쉐이크 golden_eggs = min(15, gen×3) − eggs_used 인데 한쪽만 −1 한 코드가 있었음. "지금은 아무도 안 읽어서 무해"한 시한폭탄으로 규칙집에 기록돼 있음.

멍집사 황금상자 누적 모델(재고 = 나온 수 − 오늘 연 수)이 정확히 같은 형태임. 한 항을 건드리면 나머지 항도 같은 트랜잭션에서.

B8진행률 게이지는 "확정된 것만" 그릴 것🟡 점검

캐시쉐이크 fix: show only confirmed shake progress — 서버가 확인해 준 진행분만 게이지에 반영하도록 되돌렸음. 미확정분을 미리 그리면 되돌아갈 때 유저가 "빼앗겼다"고 읽음.

멍집사 레벨·경험치 게이지, 황금상자 재고 표시가 같은 성질임. 줄어드는 방향의 정정은 유저에게 손실로 읽힌다는 걸 기준으로 볼 것.

C. 인증 · 세션 (4)

로그인은 하루에 한 번도 안 보는 화면이지만, 깨지면 앱 전체가 0이 되는 유일한 지점임.

C1일시 오류에 세션을 삭제하지 말 것 (읽기 실패 ≠ 인증 실패)🟡 계약 미명시

캐시쉐이크 "2~3일에 한 번 로그인이 풀린다" 는 제보. 서버는 정상이었고(타임박스 세션 0건), 원인은 콜드스타트 initialize()세션 복원 4초·프로필 4초 타임아웃을 넘기면 SecureStore 세션을 삭제하고 signOut 한 것. 24h auth 로그의 "토큰 회전 성공 → 6~17초 뒤 재로그인" 시그니처 5건으로 확정했음. 봉합: 파괴적 처리 전면 제거 + 타임아웃 4s→10s + 프로필 5s×2회 재시도 + 워치독 5s→25s.

멍집사 supabase-js 기본 동작에 맡기고 있어 파괴적 정리 코드가 없음 — 지금은 안전함. 다만 나중에 "부팅이 느리다"는 이유로 타임아웃+정리를 넣고 싶어질 수 있으므로, "읽기 실패로 세션을 지우지 않는다"를 주석 계약으로 박아 둘 것.

C2카카오 이메일 미인증 로그인 실패 — 유저가 조치할 수 있게🔴 미적용

캐시쉐이크 production auth 로그에 500: Error getting user email from external provider하룻밤에 19회(동일인 반복 시도). 카카오 콘솔에서 이메일이 필수 동의여도, 계정 이메일이 미인증(인증 만료) 이면 카카오가 is_email_verified=false 로 내려주고 GoTrue 가 이메일 없음으로 취급함. 앱은 error_description 을 버리고 "로그인에 실패했습니다" 만 띄워 유저가 할 수 있는 게 없었음. 봉합: 콜백 에러 분류 모듈 + 📧 "카카오톡 설정 → 카카오계정에서 이메일 등록·인증 후 재로그인" 안내.

멍집사 같은 카카오 OAuth 를 씀. login-screen.tsxreason === 'error' 를 전부 "로그인에 실패했어요. 잠시 후 다시 시도해 주세요." 로 뭉갬 — 잠시 후 다시 해도 영원히 안 됨. Top 10 #3.

C3인증 오류 원문(영문)을 유저에게 노출하지 말 것🔴 미적용

캐시쉐이크 Sentry CASHSHAKE-6 대응(v1.0.8) — 폴백이 error.message 를 그대로 돌려줘서, 누가 그 문자열을 화면에 그리는 순간 한국인 유저에게 영문 개발자 메시지가 뜨는 구조였음. 원본은 console·Sentry 로 그대로 보내되 유저 대면은 한국어로 덮음.

멍집사 performOAuthmessage: error.message 로 원문을 그대로 실어 보냄. 지금은 화면이 안 그리지만 같은 구조임 — 원문은 로그로, 화면은 한국어로 분리해 둘 것.

C4키체인 읽기 실패의 처방은 "재로그인" 이 아니라 "앱 재실행"🔴 미적용

캐시쉐이크 SecureStore/Keychain 이 "User interaction is not allowed" 로 실패할 때 화면이 "다시 로그인해주세요" 라고 안내했는데, 일시 오류에는 세션을 지우지 않으므로 실제 처방은 앱 재실행이었음. 부정확한 안내가 불필요한 카카오 재인증 왕복을 시키고 있었음.

멍집사 청크 저장까지 갖춘 secure-storage.ts 를 쓰므로 같은 오류가 발생 가능함. 안내 문구를 미리 맞춰 둘 것.

D. 시간 · 리셋 기준 (4)

리워드앱에서 "하루"는 사업 규칙임. 이게 기능마다 다르면 아무도 모르는 사이에 어긋남.

D1"우연히 맞는" 기준은 언젠가 조용히 깨짐🔴 규율

캐시쉐이크 원래 09시가 맞았던 이유는 DB 타임존이 UTC 라서 current_date 가 UTC 00시 = 정확히 KST 09시였기 때문임 — 의도가 아니라 우연이었음. 그래서 나중에 누가 (now() at time zone 'Asia/Seoul')::date 로 "고치자" 마이그가 들어오면서 조용히 00시로 이동했음.

멍집사 기준을 이름 있는 함수/설정값으로 명시해 두면 이 사고가 안 남. mj_config_int('dayResetHour', 9) 형태는 좋은 방향임 — 클라 쪽도 같은 값을 보게 유지할 것.

D2기준을 바꾸면 멱등키가 어긋나 중복 지급이 남🔴 규율

캐시쉐이크 리셋 기준을 되돌리려 했더니 스트릭 보너스 멱등키 run_start_date 가 날짜에서 파생돼, 새벽 사용자의 행이 하루 앞으로 병합되면 이미 받은 마일스톤(5·15·50·80·300원)이 재지급될 수 있었음. 그래서 "그냥 되돌리면 안 된다" 로 결론.

멍집사 출석 스트릭·미션 기간(mission_period)·황금상자 재고가 전부 날짜 파생임. 리셋 시각을 바꾸는 건 마이그 1건이 아니라 백필 포함 프로젝트라고 볼 것.

D3기준 전환일에는 반드시 누군가 손해를 봄🔴 규율

캐시쉐이크 전환일에 새벽 N회가 09시 이후에도 카운터에 남아 15−N 회만 가능, 출석은 그날 기회 상실. 전환 순서를 ①흡수상태 봉합 → ②백필 동반 전환 → ③멱등키 재계산 → ④기획 확정 → ⑤클라 문구 로 못 박았음.

멍집사 같은 성질. 전환은 이용자 수가 가장 적은 시각에, 공지 팝업(H1)과 함께 나가야 함.

D4클라의 "오늘" 은 기기 로컬 자정 — 서버 영업일과 다름🟡 점검

캐시쉐이크 todayKey()(기기 로컬 자정) 와 computeGoldenEggBoundaries()(09시) 가 공존했음. 해외 로밍·기기 시계 변경까지 겹치면 클라 표시가 서버와 갈림.

멍집사 카운트다운·재고 표시는 서버가 준 값 + 응답 시각 기준으로 환산할 것 — useAuthedSnapshot 이 이미 fetchedAt 을 주므로 그 위에 세울 것.

E. 배포 · OTA · 환경 (10)

코드가 맞아도 어디에 어떻게 나가느냐에서 캐시쉐이크는 최소 4번 크게 틀렸음.

E1app.json 에 platforms 를 지정하지 않으면 OTA 발행이 깨질 수 있음🔴 미적용

캐시쉐이크 platforms 미지정 → Expo 기본값 ios/android/web 이 적용돼 eas update --platform all 이 web 까지 export 하려다 실패했음(lottie 의 web 진입점이 미설치 패키지를 요구). OTA 발행 자체가 중단돼 그날은 ios/android 를 따로 쏘아 우회했음. 봉합은 app.json 1줄.

멍집사 app.jsonplatforms 키가 없음 — 같은 지뢰가 그대로 있음. "platforms": ["ios","android"] 추가. runtimeVersion 을 안 건드리므로 기존 OTA 호환성에 영향 없음.

E2runtimeVersion 이 앱 버전에 묶이면 버전 = OTA 도달 범위🟡 구조 동일

캐시쉐이크 runtimeVersion.policy = "appVersion" 이라 app.json 의 version 이 OTA 가 누구에게 닿는지를 그대로 결정함. 그래서 "자동 버전업 금지" 가 규칙이 됐음 — 에이전트가 임의로 올리면 릴리스 판단을 가로챔.

멍집사 version: "1.0.3" · runtimeVersion: "1.0.3" 리터럴로 동일 효과. 버전을 올리면 그 순간 기존 유저에게 OTA 가 안 닿음. 올릴 땐 스토어 빌드와 세트로만.

E3eas update 는 eas.json 이 아니라 EAS 대시보드 env 를 읽음🔴 확인 필요

캐시쉐이크 이 한 줄을 몰라서 주말 운영 OTA 가 테스트 광고 단위로 구워졌고 실유저 보상이 통째로 멈췄음(A6 참조). "빌드는 되는데 OTA 만 다르게 동작" 이라는 최악의 재현 조건이었음.

멍집사 eas.jsonenv: { APP_VARIANT }빌드에만 적용됨. eas update 전에 대시보드 production/preview 환경에 같은 값이 있는지 확인해 둘 것.

E4버전은 지시 없이 올리지 않음🔴 규율

캐시쉐이크 하루에 두 번 지시 없이 자동 bump 가 일어나(1.0.3→1.0.4, 1.0.7→1.0.8) 브랜치 운영 규칙에 철칙 B3 로 박혔음.

멍집사 멍집사·캐시독은 코드 수정 시 자동 버전업이 관행이었는데, OTA 채널이 개통된 지금부터는 캐시쉐이크와 같은 위험이 생김(E2). 자동 bump 대상에서 빼거나, 최소한 OTA 발행 여부와 세트로 판단할 것.

E5개인 브랜치(alvin)는 배포원이 아님🔴 규율

캐시쉐이크 alvin 은 운영을 따라가며 작업을 보관하는 스테이징일 뿐인데 배포원으로 오해해 "alvin 에서 OTA 쏘라"고 조언한 사고가 있었음. 승격 대상과 시점은 사용자가 따로 지시함.

멍집사 같은 alvin/develop/main 구조를 씀. alvin 기준으로 "배포 준비 끝" 이라고 보고하지 말 것.

E6운영을 따라잡을 땐 cherry-pick 복제가 아니라 머지🔴 규율

캐시쉐이크 production 에 이미 있는 커밋 6건을 alvin 에 cherry-pick 했다가 SHA 만 다른 사본이 쌓여, 나중에 머지할 때 businessTime.ts add/add + AttendanceCard.tsx content 충돌이 났음.

멍집사 git merge origin/main 으로 갈 것. 충돌 시 production 쪽 채택이 기본(따라가는 방향이므로).

E7OTA cherry-pick 에서 마이그 동반 커밋은 빼야 함🔴 규율

캐시쉐이크 7차 OTA 때 alvin 커밋 2건만 선별 cherry-pick 하고, 마이그를 동반한 커밋은 의도적으로 제외했음 — 딸려 나가면 서버가 안 따라온 상태로 클라만 나감.

멍집사 마이그 + 클라 변경이 한 커밋에 섞이면 이 판단 자체가 불가능해짐. 작업 1건 = 커밋 1건(J2)이 여기서 값을 함.

E8레포 커밋만으로 운영 DB·Edge 는 바뀌지 않음🔴 규율

캐시쉐이크 운영 프로젝트는 git 연동이 없어 CLI 수동임. 마이그 060(당첨자 마스킹)은 클라 변경 0건이라 코드는 다 됐는데 DB 적용 전까지 앱에 아무 변화가 없었음 — "다 했는데 왜 안 바뀌죠" 가 여기서 남.

멍집사 db:migrate = supabase db push. 클라 변경이 0건인 작업일수록 적용 여부를 별도로 보고할 것.

E9네이티브 스플래시는 OTA 대상이 아님🔴 인지

캐시쉐이크 스플래시 카피를 OTA 로 바꿨는데 앞부분 로고만 나오는 구간(expo-splash-screen)은 바이너리에 박혀 있어 안 바뀜. 거기를 바꾸려면 빌드 + 스토어 심사가 필요함.

멍집사 동일. "스플래시 바꿔주세요" 요청이 오면 어느 구간인지 먼저 가를 것.

E10OTA 의 runtime 이 실유저 바이너리와 일치하는지 확인🔴 규율

캐시쉐이크 stale 한 remote ref 로 "main=1.0.6 이라 OTA 가 실유저(1.0.7)에게 도달 안 함" 이라고 두 차례 오판했음. 실제로는 같은 날 개발자가 머지해서 이미 일치한 상태였음. 규칙: 브랜치·버전 상태를 단정하기 직전마다 git fetch.

멍집사 발행 후 eas update:list 로 runtime·커밋을 대조하는 절차를 고정할 것.

F. 레이아웃 · 반응형 (10)

캐시쉐이크는 좁은 화면 대응에만 6커밋 + 픽셀 실측을 썼음. 멍집사는 아직 이 스윕을 한 번도 안 했음.

F1한글은 기본값이 음절 단위로 꺾임 — 어절 보호가 필요함🔴 미적용

캐시쉐이크 갤럭시 플립에서 "아메리카/노 T", "받아/요", "않/아요", "자동으/로" 처럼 단어 한복판이 꺾였음. 봉합은 keepAllKorean 유틸 — 어절 내부에 WORD JOINER(U+2060) 를 넣어 공백에서만 줄바꿈되게 하고, iOS 는 lineBreakStrategyIOS="hangul-word" 병행.

멍집사 코드 전체에 U+2060·lineBreakStrategy 사용이 0건. "브랜드콘", "황금상자", "졸업 시키기" 같은 단어가 SE·플립에서 꺾일 수 있음. 유틸 1개 + 적용 지점 목록이면 됨.

F2RN 의 flexShrink 기본값은 0 — 라벨이 값을 밀어냄🔴 점검

캐시쉐이크 320dp 에서 라벨↔값 겹침이 4곳 나왔음. 원인은 웹 CSS(1)와 달리 RN 의 flexShrink 기본이 0 이라 좌측 라벨이 줄지 않고 우측을 밀어낸 것. 봉합: 좌측 flex:1 + minWidth:0, 우측 flexShrink:0, 숫자·라벨은 adjustsFontSizeToFit.

멍집사 좌우 배치 행(잔고·쿨타임·레벨 헤더 등)이 많음. 콜드 스타트로 재검증할 것 — fast-refresh 는 flex 재계산이 누락될 수 있음(캐시쉐이크 실측 메모).

F3320dp · 360dp 실측 스윕을 한 번은 돌릴 것🔴 미적용

캐시쉐이크 QA No.79 — 갤럭시 플립 사용자가 360dp 에서 UI 가 깨진다고 제보. 여기에 "화면 크게" 설정이 겹치면 320dp 까지 내려감. 폴더블 에뮬(density 480/540)로 조건을 재현해서 잡았음.

멍집사 기준 3종만 고정하면 됨 — 320dp(플립+확대) / 375pt(iPhone SE) / 360dp(폴더블 접힘).

F4가로 정렬 칩은 줄바꿈 대신 축소🔴 미적용

캐시쉐이크 칩 5개가 가로로 늘어선 카드에서 한 칩만 줄바꿈되면 높이가 통째로 틀어짐. numberOfLines=1 + adjustsFontSizeToFit(min 0.75) 로 꺾는 대신 줄였음.

멍집사 미션 보드·연속 출석·황금상자 시각 칩이 같은 구조임.

F5최대 가로폭을 정하고 좌우 여백을 남길 것🔴 미적용

캐시쉐이크 QA No.46·49 — "반응형이 1차원적으로 들어가서" 넓은 화면에서 버튼이 가로로만 길어져 형태가 이상해짐. 최대폭 480 으로 잡고 남는 폭은 좌우 여백으로 돌렸음.

멍집사 모달만 maxWidth 가 있고 본문 컨테이너에는 없음. 태블릿·큰 폰에서 버튼이 늘어짐.

F6앱 전역 당겨서 새로고침🔴 미적용

캐시쉐이크 QA No.60 로 시작해 공용 useAppRefresh으로 탭 4개 + 스택 4개(11파일)에 깔았음. 가드 2개가 핵심: 중복 당김 차단, 실패 시 삼킴(RefreshControl 은 promise 를 처리 안 해 reject 가 unhandled rejection 이 됨). 폼 화면(출금·쿠폰)은 일부러 제외했음.

멍집사 RefreshControl 사용 0건. 값이 어긋났을 때 유저가 할 수 있는 게 앱 재시작뿐임 — B4(자가 회복)와 짝으로 넣으면 효과가 큼.

F7스크롤 어포던스 — 아래에 더 있다는 걸 보이게🔴 미적용

캐시쉐이크 QA No.103 + 바운스 셰브론 힌트(3d6e445) — 바텀네비를 반투명으로 깔아 콘텐츠가 그 아래로 이어진다는 신호를 줬음.

멍집사 홈·미션·앨범이 긴 화면임. 하단이 딱 잘려 보이면 유저가 스크롤을 안 함.

F8줄바꿈은 \n 으로 고정 — 기기 폭에 맡기지 말 것🔴 미적용

캐시쉐이크 서브 카피를 폭에 맡겼더니 좁은 화면에서 현금으로 / 출금까지 로 어색하게 끊겼음. 쉼표 위치에 \n 고정으로 해결. 안내 카드 라벨도 같은 처리를 해서 "자동 개행 자체가 없으므로 재배치 위험도 없음" 상태로 만들었음.

멍집사 2줄로 읽혀야 하는 카피(스플래시·이벤트 안내·모달 본문)에 적용.

F9스플래시 강제 로딩 시간을 없앨 것🟣 검토

캐시쉐이크 QA No.104 — "실로딩이 아니라 강제로 잡혀 있는 듯. 절반으로 줄여도 됨". 첫인상 구간에서 아무 일도 안 하면서 기다리게 하는 시간은 이탈로 직결됨. QA No.48 의 "스플래시 전에 이미지가 확 확대되는 현상 — 1회지만 첫 경험부터 별로" 도 같은 맥락.

멍집사 부팅 시 configReady 대기가 있음. 실제로 필요한 대기인지, 아니면 연출 때문인지 1회 실측할 것.

F10좁은 화면 눌림은 픽셀 실측으로 닫을 것🔴 규율

캐시쉐이크 "최장 라벨 43pt vs 칸 너비 SE 75.8pt" 처럼 숫자로 닫았음. 스피너↔푸터 간격도 SE 에서 16pt 까지 붙은 걸 실측해 36pt 로 벌렸음.

멍집사 "괜찮아 보인다" 로 끝내지 말 것 — 가장 긴 문자열과 가장 좁은 칸의 숫자를 비교할 것.

G. 모달 · 인터랙션 (4)

"버튼을 눌렀는데 반응이 늦다" 는 제보의 진짜 원인이 애니메이션이 아니라 JS 스레드 점유였던 사례가 두 번 있었음.

G1OS Alert 대신 인앱 모달로 통일🟡 부분

캐시쉐이크 안내가 iOS 시스템 알림풍 다크 회색 박스라 앱 디자인과 이질적이고 위계도 없었음. AppNoticeModal 로 교체하며 호출부 문자열은 그대로 두고 중앙 매핑이 첫 문장을 타이틀로 분리 + 키워드로 아이콘/변형을 결정하게 했음(새 메시지는 ℹ️ 안전 폴백).

멍집사 Alert.alert 가 2곳 남아 있음(브랜드콘 상세·설정 로그아웃). action-confirm-modal 이 이미 있으므로 교체 비용이 낮음.

G2"직전과 같은 메시지면 생략" 가드는 버그가 됨🟡 경증

캐시쉐이크 prevMessage 가드 때문에 같은 안내(쿨타임 등)가 세션당 1회만 표시되고 그 뒤로는 아무 반응이 없었음. 다크 모달 시절부터 있던 잠복 결함이었고, 시연 중에 발견해서 가드를 제거했음.

멍집사 명시적 가드는 없어 같은 사고는 안 남. 다만 NoticeBanneruseEffectmessage 문자열을 키로 돌아서, 배너가 떠 있는 동안 같은 문구가 다시 발화하면 5초 타이머가 재시작되지 않음(예상보다 빨리 사라짐). 발화 카운터를 키에 섞으면 끝남.

G3쿨타임 안내는 카운트다운 + 0초 자동 닫힘🔴 미적용

캐시쉐이크 "N초 후 흔들 수 있어요" 를 실시간으로 줄이고 0초에 스스로 닫음. 정적 문구는 유저가 언제 다시 눌러야 할지 모름.

멍집사 ad-gate 가 이미 ${cooldownLeft}초 후 가능 을 계산함 — 모달/버튼 라벨이 이 값을 매초 반영하는지 확인할 것(use-countdown 이 이미 있음).

G4모달 지연의 마지막 성분은 네이티브 Dialog 왕복🔴 인지

캐시쉐이크 rAF 전환 후에도 아주 구형 칩셋에는 잔여 지연이 남았고, 남은 성분은 탭→JS→네이티브 Dialog 해제 왕복 비용으로 진단했음. 다음 레버는 Modal인트리 오버레이 교체.

멍집사 보상 모달을 Modal 로 쓰고 있다면, 저가 안드로이드 제보가 들어왔을 때 이게 마지막 카드임.

H. 어드민 · 운영 도구 (10)

1인 운영에서 어드민은 앱 배포 없이 손댈 수 있는 유일한 손잡이임. 없으면 모든 문제가 릴리스가 됨.

H1어드민 공지 팝업 + "확인한 유저에겐 재노출 안 함"🔴 미적용

캐시쉐이크 QA No.52·111 — 광고 장애 안내를 띄울 채널이 필요해 app_popup 테이블 + RPC(마이그 064)를 신설. 처음엔 켜져 있는 동안 콜드 스타트마다 매번 떠서, updated_at 을 1회 노출 키로 쓰도록 봉합했음 — 어드민이 다시 저장하면 새 공지로 간주해 전원에게 다시 1회. 실패는 표시 쪽으로 open(기록 읽기 실패 → 표시).

멍집사 어드민에 팝업 메뉴가 없고 앱에도 수신부가 없음. 알릴 방법이 푸시뿐인데 푸시는 거부한 유저에게 안 감. Top 10 #7.

H2새 RPC 는 public·anon 실행 권한을 명시적으로 회수할 것🔴 규율

캐시쉐이크 팝업 RPC 배포 직후 revoke ... from public, anon 으로 PUBLIC EXECUTE 상속을 막는 후속 마이그가 필요했음(3a19da6). 마이그 032·056 도 같은 계열의 일괄 하드닝이었음.

멍집사 RPC 를 추가할 때마다 기본 권한이 무엇인지 확인. "되면 됐다" 로 넘기면 나중에 감사에서 한꺼번에 나옴.

H3설정 변경이 "다음 부팅" 이면 장애 대응이 안 됨🔴 미적용

캐시쉐이크 마이그 025 로 보상 설정을 realtime 구독으로 만들었음. 장애 중에 값을 바꾸면 앱을 다시 켜지 않아도 반영됨.

멍집사 어드민 저장 배너가 대놓고 "모바일은 다음 부팅 때 새 값을 받아갑니다" 라고 말함. 킬 스위치(A7)를 넣더라도 이게 없으면 반영까지 몇 시간이 걸림 — 둘은 세트임.

H4강제 업데이트는 "실수로 켜지지 않게" 분리할 것🔴 실제로 있음

캐시쉐이크 어드민의 "설정 저장" 버튼은 최소 버전을 저장하지 않음(metadata 전용). 최소 버전 변경·강제 업데이트 켜기는 빨간 버튼 "이 버전을 최소 버전으로 적용" 이 유일한 경로 — 의도된 안전장치(마이그 059). 앱 체크는 콜드 스타트 1회 + 정책 조회 2초 fail-open.

멍집사 minAppVersion다른 밸런싱 값들과 같은 폼의 자유 입력 칸임(game-config-form.tsx). 검증은 /^\d+(\.\d+){0,3}$/ 정규식뿐이라 9.9.9 를 실수로 저장하면 전 유저가 앱을 못 씀 — 되돌리려면 다시 어드민에 들어가야 하는데, 그 사이 유저는 강제 업데이트 화면에 갇힘. 별도 확인 단계 + "현재 출시 버전보다 높은 값" 차단이 필요함. 조회 실패 시 fail-open 인지도 같이 확인할 것.

H5설정 화면에 "최신버전" 배지🔴 미적용

캐시쉐이크 QA No.94 — 유저가 스토어에 가보지 않아도 알게. 강제 업데이트가 이미 쓰는 서버 정책을 재사용하고, 판정 불가(네트워크 실패·정책 미설정·파싱 불가)면 배지만 조용히 생략. 실패모드 최악이 "배지 안 뜸" 이라 반경이 0 에 가까움.

멍집사 minAppVersion 은 있는데 latestVersion 이 없음. config 키 1개 + 배지 1개.

H6유저가 올린 이미지·요청은 어드민에서 볼 수 있어야 함🔴 점검

캐시쉐이크 QA No.68 — 리뷰 이벤트 인증샷을 어드민에서 볼 수 없어 지급 판단 자체가 불가능했음. 사용자 탭 아래 신규 탭으로 신설.

멍집사 브랜드콘 쿠폰 요청(전화번호)·초대 검증이 같은 성질임. 지급/발급을 사람이 판단하는 흐름은 전부 어드민 화면이 선행조건.

H7운영 화면에서까지 마스킹하면 운영이 안 됨🔴 규율

캐시쉐이크 QA No.20 — 출금 신청자 계좌번호가 어드민에서도 마스킹돼 있어 이체를 보낼 수가 없었음. 보안 조치가 업무를 막은 사례임.

멍집사 브랜드콘 발급용 전화번호가 같은 위치임. 유저 대면은 마스킹, 운영 화면은 원본 + 접근 감사 로그 가 올바른 조합임(audit 페이지가 이미 있음).

H8지급 주기 문구는 실제 운영과 반드시 일치해야 함🔴 규율

캐시쉐이크 "매달 1일 일괄" → "바로 다음날" 로 바꾸면서, 같은 정책을 말하는 유저 노출 문구 4곳을 한 번에 바꾸고 grep 으로 구 문구 잔존 0건을 확인했음. 그리고 봉합 노트에 "지급은 어드민 수동 — 문구가 먼저 바뀌므로 실제 운영이 지켜져야 클레임이 없음" 을 남겼음.

멍집사 브랜드콘 발급 소요·초대 보상 조건 문구가 여러 화면에 흩어져 있음. 정책 1개 = 문구 N곳을 항상 세트로 볼 것.

H9어드민의 강제 조정이 앱에 반영되는지 확인🟡 점검

캐시쉐이크 QA No.3 — 어드민에서 잔액을 강제 조정했는데 유저에게 반영이 안 돼 출금 테스트 자체가 불가능했음. 어드민 기능이 "저장은 되는데 안 보이는" 상태였음.

멍집사 admin_reward_and_media_controls 로 지급 도구가 있음. 지급 후 앱에서 실제로 보이는지를 QA 항목으로 고정할 것.

H10자동 추첨·자동 지급 cron 은 "실제로 돌았는지" 를 봐야 함🟡 점검

캐시쉐이크 QA No.18 — 응모는 마감됐는데 추첨이 실행되지 않아 당첨자가 0명이었음. cron 은 조용히 안 도는 게 기본 실패모드임.

멍집사 푸시 outbox·cron extension 이 있음. 마지막 실행 시각을 어드민 대시보드에 노출해 두면 안 돈 걸 눈으로 잡을 수 있음.

I. 스토어 심사 · 정책 · 어뷰징 (5)

리워드앱은 거절 사유가 정해져 있음. 캐시쉐이크가 이미 걸려본 것들임.

I1확률형 요소는 앱 안에서 규칙·확률에 닿을 수 있어야 함🟡 절반만

캐시쉐이크 QA No.22 — 앱스토어 심사 통과를 위하여 응모하기 상단에 이벤트 규칙 버튼을 신설했음(중요도 상). 심사뿐 아니라 확률형 고지 관점에서도 필요함.

멍집사 황금상자에는 있음 — 상단 '이벤트 안내'가 시간창·초기화·완주 조건·보상 범위를 펼쳐 줌(주석에 "예전에는 눌리지 않는 텍스트라 확인할 경로가 없었다" 는 봉합 기록까지 있음). 그런데 뽑기(gacha) 화면에는 같은 경로가 없음 — 같은 확률형인데 한쪽만 고지돼 있음. 황금상자 규칙 블록을 그대로 재사용하면 됨.

I2타인에게 보이는 닉네임은 마스킹🟡 점검

캐시쉐이크 QA No.82 — 당첨자 이름을 *나다* 형태로. 처음엔 빠**8(앞1+뒤1)였는데 당첨 실감이 약해 "끝 가림·가운데 공개" 로 바꿨음. 마스킹은 서버 RPC 소관이라 클라 변경 0건이었음.

멍집사 display-name.ts 가 있음. 랭킹·자랑 기능을 넣을 때 서버에서 마스킹해서 내려줄 것 — 클라에서 자르면 원본이 이미 기기에 도착한 뒤임.

I3탈퇴 → 재가입 어뷰징 가드가 없음🔴 실제로 없음

캐시쉐이크 마이그 034(탈퇴 어뷰징 가드) → 061(identity fingerprint 삭제 가드)로 두 번 손봤음. 탈퇴로 신규 보상을 반복 수령하는 경로를 막는 게 목적이었고, 한 번에 못 막아서 두 번 판 것임.

멍집사 delete_account_archive브랜드콘 주문을 세무·감사용으로 익명 보관할 뿐, 재가입을 막는 지문은 없음(마이그 전체에 fingerprint/identity 가드 0건). Supabase 는 auth 유저를 지우면 같은 카카오 계정도 새 uid 를 받으므로, 탈퇴 → 재가입으로 signupFood 3 · signupToys 3 · signupEnergy 100 과 입양 기본 지급을 반복 수령 가능함. 광고를 안 보고도 자원이 생기는 유일한 경로라 정확히 어뷰저가 찾는 구멍임. 최소한 이메일 해시 + provider 를 탈퇴 시각과 함께 남겨 재가입 시 신규 보상만 건너뛰게 할 것.

I4한 사람이 여러 계정을 만들 때의 기준은 "계정" 이어야 함🟡 점검

캐시쉐이크 QA No.66 — 같은 기기에서 아이디 2개를 쓰면 차감이 두 계정에 동시 적용됐음. 요청은 반대 방향이었음 — 디바이스 기준이 아니라 아이디 기준으로 차감(광고 수익 향상). 즉 어뷰징 차단과 수익은 같은 축이 아님.

멍집사 한도는 계정 기준. 다만 초대 보상은 계정만으로 막을 수 없으므로 별도 기준이 필요함(마이그 push_outbox_and_invite_guards 확인).

I5앱 이름·용어를 하나로 통일할 것🔴 점검

캐시쉐이크 QA No.34 — "리워드 · 포인트 · 적립금 · 원" 4가지가 동시에 쓰이고 있었음(중요도 상). 포인트로 통일하는 데 화면 여러 곳을 훑어야 했고, "총 적립금 → 누적 포인트", 나중엔 "누적 → 보유 포인트" 로 두 번 바꿨음.

멍집사 사료·장난감·에너지·경험치·브랜드콘·포인트 등 재화 이름이 여럿임. 지금 한 번 표를 만들어 고정해 두는 게 나중보다 압도적으로 쌈.

J. 작업 규율 — 이게 진짜 자산임 (10)

캐시쉐이크가 남긴 것 중 코드보다 오래 가는 부분. 대부분 사고 한 번에 하나씩 생겼음.

J1수정 전 파급영향 점검 5단계🔴 습관화

캐시쉐이크 황금알 사건에서 1차 처방이 2차 버그를 만드는 일이 실제로 있었음. 그래서 규칙이 됨: ①심볼 grep 전수(요약 금지) ②전역 게이트 접촉 여부 ③실패모드 비교표 ④깨지는 기존 테스트 ⑤서버·클라 양쪽.

멍집사 이미 phantomz 전역 룰(R0)로 승격돼 있음. 멍집사도 A등급 취급할 것 — 실물 기프티콘을 주는 앱임.

J2작업 1건 = 커밋 1건🔴 습관화

캐시쉐이크 5차 작업부터 회차 규칙으로 명시. 덕분에 OTA 때 2건만 선별 cherry-pick 하고 마이그 커밋을 뺄 수 있었음(E7). 묶여 있었으면 불가능했음.

멍집사 되돌리기·선별 배포·리뷰 범위가 전부 커밋 입도에 달려 있음.

J3회귀 테스트에 "순서 역전" 케이스를 반드시 넣을 것🔴 규율

캐시쉐이크 진단 문서에 이렇게 적혀 있음 — "QA85 3차 봉합이 테스트 271건이 통과하는 동안 살아남은 전례가 있음". 통과하는 테스트 수는 안전의 증거가 아님. 요구한 케이스는 CLOSED→EARNED, 프리로드 후 EARNED 도착 2가지.

멍집사 ad-watch-judge.test.ts 가 이걸 이미 잠그고 있음. 비동기 이벤트를 다루는 새 코드마다 같은 질문을 할 것 — "순서가 뒤집히면?"

J4실패모드가 나빠지는 수정안은 폐기🔴 규율

캐시쉐이크 황금알 처방에서 syncShakeStateFromServer() 무조건 호출은 금지로 결론났음 — 그 함수가 전역 게이트를 건드려 "광고 낭비" 를 "아예 못 흔듦" 으로 바꿨을 것이기 때문. 심각도가 오르면 그 안은 버림.

멍집사 비교 축 4개를 그대로 쓸 것: 방향 · 지속성 · 자가치유 · 최악 결과.

J5진단은 추측이 아니라 실데이터로🔴 규율

캐시쉐이크 "광고가 안 나온다" 제보를 AdMob 리포트 API 필률 + 운영 SQL 전수 + 24시간 통합 로그로 분해해서, 인프라·서버·버전 스큐를 전부 무죄로 확정한 뒤 클라 한 곳으로 좁혔음. "필률이 정확히 100.0%" 라는 숫자 하나가 NO_FILL 이 아니라 NETWORK_ERROR 라는 결론을 만들었음.

멍집사 Supabase 로그 + Sentry + AdMob 리포트가 다 있음. 추측으로 고치지 말 것.

J6코드를 안 고치고 진단만 전달하는 회차를 허용할 것🔴 규율

캐시쉐이크 260802 회차는 "코드 수정은 하지 않았습니다 — 진단·처방만 전달드립니다" 로 끝났음. 이슈 3건 중 2건은 반경이 커서 손대면 안 되는 상태였음. 이게 가장 값진 회차 중 하나였음.

멍집사 "뭐라도 고쳐야 한다" 는 압박이 J4 를 어기게 만듦.

J7문구 변경은 grep 으로 잔존 0건을 확인할 것🔴 규율

캐시쉐이크 리뷰 보상 문구를 바꾸며 4곳 동시 변경 + 코드 주석 동기화, 그리고 구 문구 유저 노출 잔존 0건을 grep 전수로 확인했음. 회피성 문구 순화 때도 "앱 전체에서 이 1곳뿐" 을 먼저 확인했음.

멍집사 문구가 화면·모달·푸시·웹(약관/Q&A)에 흩어져 있어 더 넓음.

J8기존 테스트가 잠근 계약은 지우지 말고 의도적으로 재작성🔴 규율

캐시쉐이크 로그인 봉합에서 기존 6건을 "purge credentials" → "keep credentials" 로 반전시키고, 그 사실을 보고에 명시했음. 구 코드에서 4건 실패 재현(RED) → 봉합 후 GREEN 까지 확인.

멍집사 깨지는 테스트는 우연이 아니라 과거의 설계 결정임. 왜 그 계약이 있었는지 확인하고 바꿀 것.

J9TDD — 선작성 후 RED→GREEN🔴 습관화

캐시쉐이크 닉네임 버튼 3건·응모 안내 카드 4건·오류 안내 10건 등 UI 변경조차 테스트를 먼저 썼음. 라벨·내비게이션·문구 존재 같은 얕은 계약이라도 있으면 회귀가 잡힘.

멍집사 vitest + 순수 모듈 분리 구조가 이미 이 방식에 맞게 짜여 있음(ad-watch-judge·version-compare 가 RN import 를 피한 이유).

J10실기 캡처 없이 "됐다" 라고 하지 말 것🔴 규율

캐시쉐이크 매 회차에 iPhone 17 Pro + SE 3rd Release 빌드 실기 캡처가 붙어 있음. 변경 전후 빌드 캡처를 같은 배율로 확대 대조해서 폰트 폴백 결함을 찾아냈음.

멍집사 광고 실패 화면처럼 실제로 실패시켜야 보이는 경로는 캡처가 어려움 — 그럴 땐 "캡처 없음" 을 명시할 것(캐시쉐이크도 그렇게 했음).

K. 성장 · 리텐션 — 캐시쉐이크가 아직 못 한 것 (11)

QA 시트의 보류·미완료 칸에 남아 있는 것들임. 캐시쉐이크가 못 한 건 멍집사가 먼저 해도 된다는 뜻이기도 함.

K1온보딩 튜토리얼 — "너무 뜬금없이 앱이 시작됨"🟣 미해결

캐시쉐이크 QA No.108 그대로 인용: "너무 뜬금없이 앱이 시작되긴 함". 조건부로 뜨는 온보딩이 필요하다고 적어 뒀지만 아직 미착수.

멍집사 first-run.ts + 스탯 안내 시트로 부분적으로 이미 있음 — 여기서 조금만 더 밀면 캐시쉐이크를 앞지름. 강아지 입양 → 먹이 → 레벨업까지가 첫 세션의 성공 조건임.

K2업적 — "도달하고 싶고 자랑하고 싶은 것"🟣 미해결

캐시쉐이크 QA No.114 — "유저 입장에서 뭔가 도달하고 싶은, 자랑하고 싶은 업적이 있어야 재미 요소가 가미됨". 흔들기만으로는 서사가 안 생김.

멍집사 앨범(도감) · 졸업 · 견종 수집 이 이미 업적의 형태를 하고 있음 — 캐시쉐이크가 갖고 싶어 한 걸 구조적으로 갖고 있는 셈. 남은 건 밖으로 자랑할 수단(공유 카드·프로필 배지).

K3"최대치" 표기가 부담으로 읽힘🟣 미해결

캐시쉐이크 QA No.109 — 알 최대 15개인데 일부 유저가 오해하고 부담으로 느낌. "최대치를 없앨까" 까지 검토했음.

멍집사 에너지 상한(5,000)·일 광고 한도·상자 개수가 같은 성질임. 남은 것을 보여줄지 다 채워야 할 것을 보여줄지는 같은 데이터의 다른 프레이밍임.

K4소진 상태의 문구가 "고장" 으로 읽히지 않게🟣 미해결

캐시쉐이크 260813 조사 결론 중 가장 큰 것 — "광고가 안 나온다" 신고 대부분이 결함이 아니라 문구 문제였음. 09시 일괄 지급으로 바꾼 뒤 흔든 유저의 58.9% 가 중앙값 14:17 에 전량 소진했고, 그 뒤 평균 16.7시간을 "광고가 안 뜨는 상태" 로 썼음. 유저가 보는 건 18시간짜리 카운트다운뿐이라 고장난 타이머로 오독됐음. 처방은 "오늘 15회를 다 쓰셨어요. 내일 오전 9시에 다시 충전돼요" — JS 전용.

멍집사 일 광도·뽑기·상자가 소진되는 시간대가 있음. "없음" 이 아니라 "다 썼음 + 언제 돌아옴" 으로 말할 것. 비용 대비 효과가 이 목록 전체에서 손꼽힘.

K5푸시 알림 전략화🟣 미해결

캐시쉐이크 QA No.121 — "푸시 알림 전략화 필요" 한 줄로 보류돼 있음. 모닝펄스(09시)·초대 마감 리마인드 같은 개별 발송은 만들었지만 전략은 없었음.

멍집사 푸시 캠페인 어드민 + outbox 파이프라인이 이미 있음. 재료는 갖췄으니 언제·누구에게·몇 번만 정하면 됨 — 에너지 회복·상자 충전·브랜드콘 만료 임박이 자연스러운 트리거임.

K6어트리뷰션(Airbridge) — 광고비 태우기 전에🟣 미해결

캐시쉐이크 QA No.122 — "본격 광고비 태우는 시점" 에 도입으로 적혀 있음. 없으면 어느 채널이 돈값을 하는지 모름.

멍집사 유료 광고를 시작하기 전에 붙여야 의미가 있음(설치 소급 불가). 초대 딥링크가 이미 있으니 붙이는 비용은 낮음.

K7공식 홈페이지 — 앱테크 검색·블로거 유입🟣 미해결

캐시쉐이크 QA No.115 — 체리피커·앱테크 검색 유저·블로거를 위한 홈페이지(SEO + 서비스 신뢰도). 보류 상태.

멍집사 mungjibsa-official Vercel 프로젝트가 이미 존재함. 콘텐츠만 채우면 됨 — 캐시쉐이크보다 앞서 있음.

K8누적 지급액 · 수령 후기 노출🟣 미해결

캐시쉐이크 QA No.87 — 지갑에 서비스 누적 지급액과 출금 후기를 보여주자는 안. "진짜 주는 앱인가" 를 해소하는 장치임.

멍집사 브랜드콘은 실물 기프티콘이라 이 효과가 더 큼. 발급 누적 수·최근 발급 브랜드 정도만 보여줘도 됨(개인 식별 없이).

K9앱 소개 영상 → 유료 광고 소재🟣 미해결

캐시쉐이크 QA No.125 — 광고 영상을 만들어야 유료 집행이 가능하다고 적혀 있음. 아직 없음.

멍집사 강아지 키우기라 영상 소재가 훨씬 잘 나옴(입양 → 성장 → 졸업 → 기프티콘). 캐시쉐이크의 "흔들기" 보다 30초 서사를 만들기 쉬움.

K10스토어 스크린샷은 버전 업 시점에 같이 갱신🟣 미해결

캐시쉐이크 QA No.123 — "1.0.8 업그레이드 시점" 에 스크린샷 갱신 필요로 남아 있음. 화면이 바뀌었는데 스토어는 옛날 화면인 상태가 계속됨.

멍집사 릴리스 체크리스트에 스크린샷 갱신 칸을 넣어 둘 것.

K11수동 정산은 주기를 짧게🟣 참고

캐시쉐이크 QA No.107 — 스토어 리뷰 보상을 "최대 한 달을 기다리는 건 무리" 라 판단해 1주 단위 정산으로 바꿨음. 단순 조회 업무라 주기를 줄여도 운영 부담이 안 늘었음.

멍집사 브랜드콘 발급이 사람 손을 타는 구간이면 같은 판단이 필요함 — 기다리는 시간이 길수록 "안 주는 앱" 으로 읽힘.

🔴 이 결론을 깨는 증거

가장 약한 가정 — 이 문서는 멍집사 코드를 정적으로 읽어 "있다/없다" 를 판정했음. 실제로 초안을 쓴 뒤 코드를 다시 대조했더니 6건이 뒤집혀 기반영으로 넘어갔음(보상 모달 3종 · 재진입 잠금 · 개인화 광고 · 폰트 폴백). 반대로 B1(미확정 0 표시) · H4(minAppVersion 자유 입력) · I3(재가입 어뷰징) 는 추정이 아니라 코드에서 확인된 실재로 승격됐음. 즉 남은 🔴 중에도 오판이 더 있을 수 있음 — 특히 F2(라벨 겹침) · F3(320dp) 는 실기기를 열어야 확정되고, A6 · E3(EAS 대시보드 env) 는 콘솔을 봐야 확정됨. 이 4건은 확정이 아니라 점검 지시로 읽을 것.

반대 증거로 뒤집힐 수 있는 것 — A5(SSV custom_data)는 멍집사가 이미 70자로 운영 중인데 정산이 도착하고 있음이 관측됨. 즉 캐시쉐이크의 실패 원인이 "길이" 가 아니라 콜론(:) 이나 URL 인코딩이었을 가능성이 남아 있음. 그래서 가드를 넣을 땐 "길이 상한" 이 아니라 "검증된 형태에서 벗어나지 말 것" 으로 표현해야 안전함.

이 목록의 한계 — 기반영 38건을 뺐으므로, 이 문서만 보면 "멍집사가 이미 지키고 있는 계약" 은 보이지 않음. 즉 여기 없는 항목을 "안 해도 되는 것" 으로 읽으면 안 됨 — 되돌리면 안 되는 것들이 그쪽에 있음. 그리고 🔴 58건을 다 하려 하지 말 것. 이 목록의 값은 개수가 아니라 Top 10 의 순서에 있음.