d3ro-voice/docs/v3/play/04-release-checklist.md

345 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Google Play 출시 체크리스트
기준일: 2026-08-29. 체크 표시는 이 문서를 읽은 사람이 실제 증거를 확인한 뒤에만 바꾼다. 소스 검사, debug/E2E APK, HTTP 200 하나만으로 release GREEN을 선언하지 않는다.
### 2026-08-29 Play Console live 스냅샷
| 항목 | 확인된 현재 사실 | 판정 |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| 앱 | `D3RO Voice`, package `com.d3ro.voice`, app ID `4976102237698469110` | 생성 완료 |
| Console 상태 | `임시`; 첫 AAB 업로드 0건, 설정 0/11 | 제출 불가 |
| release identity | versionName `1.1.0`, Android versionCode `1010001` | 소스 SSOT에서 확정; AAB 증거는 아직 없음 |
| Play app-signing SHA-256 | `01:00:19:21:DB:4F:33:40:85:CE:21:E4:B8:DE:CC:BD:71:DA:87:67:C5:6E:3B:59:83:2A:A1:C8:29:EA:0D:AB` | Console 실확인 |
| upload key | 외부 PKCS12·user-only ACL·Credential Manager·private-key readback GREEN; cert SHA-256 `4F:AC:69:24:82:1C:50:DA:AB:ED:76:49:32:A5:3C:48:6F:8C:6C:5F:34:B9:F1:8D:B9:20:AA:40:99:15:2B:54` | local key 준비. Console 표시·AAB signer 대조는 첫 AAB 후 |
| release evidence | Ed25519 keypair roundtrip GREEN; public key·keyId `2797d3e63affd2348941bc2871b57e520c958f7f5da1a4bbe8aa46904a890b7f` SSOT | CI private-key secret·복구 백업 미완료 |
| Firebase | Google Console `u/0`, `u/1` 모두 D3RO project 없음 | 프로젝트·Android 앱 생성은 사용자 승인 필요 |
| closed test | 0명 / 요건 12명, 연속 14일 | 미시작 |
| production access | disabled | closed test와 access 신청·승인 전까지 출시 불가 |
외부 법적·정책 선언은 일반적인 배포 지시로 대신하지 않는다. Console의 실제 문구를 사용자가 검토하고 action-time 승인하기 전에는 저장·제출하지 않는다.
## P0 — 현재 제출 차단 요약
- [x] 사용자 본인이 Play 개발자 정책과 미국 수출법 선언을 검토·승인하고 앱 생성을 실행했다: app ID `4976102237698469110`.
- [ ] AI 생성 결과의 인앱 신고와 운영 moderation 원장이 production에서 E2E 검증됐다.
- [ ] 개인정보처리방침에 AdMob 자동 데이터와 AI/UGC 신고 데이터가 구체적으로 반영됐다.
- [x] 기본 Android Studio launcher icon을 D3RO legacy/adaptive/monochrome icon source로 교체하고 512×512 Play icon을 만들었다.
- [x] JDK 17 양 ABI debug build와 기존 데이터 보존 update install 후 Pixel Launcher emulator의 adaptive circle mask를 확인했다.
- [ ] Play-signed release 설치본과 실제 Pixel/Samsung 등 OEM mask·monochrome scale을 확인했다.
- [x] 1024×500 feature graphic A-D 역사 후보와 SVG 원본·PNG 해시·재현 절차를 보존했다.
- [x] canonical icon을 reference로 한 imagegen E 원본·prompt·1024×500 제출본·해시를 준비하고 선택했다.
- [x] Console preview에서 feature graphic E의 safe crop·thumbnail contrast·규격 경고 없음을 확인했다.
- [ ] 실제 release 앱 스크린샷을 준비했다.
- [x] Console에서 Play app-signing certificate SHA-256을 확인했다.
- [ ] 첫 AAB 업로드 후 Console이 표시하는 upload certificate가 준비한 key의 SHA-256과 일치하고 Play app-signing certificate와 다름을 증명했다.
- [ ] source `assetlinks.json` 3개는 Play app-signing certificate로 교체했지만 live는 아직 구 certificate다. 같은 소스를 배포하고 public HTTPS 응답을 재검증한다.
- [ ] Firebase production 프로젝트/Android 앱/`google-services.json`과 Play app-signing fingerprint를 연결했다.
- [ ] production secrets로 signed AAB를 만들고 package/version/config/signature/ABI/16 KB page size를 검증했다.
- [ ] Play internal track에서 **Play가 서명한 설치본**으로 광고, 결제, FCM, App Links, 녹음 FGS, AI 신고, 계정 삭제를 실기기 E2E 검증했다.
- [ ] reviewer용 Free와 Pro+ 재사용 계정, FGS 영상 URL, Data safety 최종 답을 Console에 입력했다.
- [ ] 현재 확인된 요건대로 12명을 모집해 연속 14일 closed test를 완료하고 production access를 승인받았다. 현재 0명이고 access는 disabled다.
## 1. 개발자 계정과 앱 생성
### 확인된 준비값
| 항목 | 값 | 상태 |
| -------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------- |
| Google 계정 | `yunchan8804@gmail.com` | live handoff에서 확인. 제출 직전 account switcher 재확인 |
| 개발자 | `YUN CHAN` | 개인 개발자 계정 |
| 개발자 계정 ID | `7374613349497835192` | live handoff 값. Console URL/계정 화면 재확인 |
| 앱 이름 | `D3RO Voice` | 생성 완료 |
| package | `com.d3ro.voice` | 생성 시 사용 가능 확인 후 등록 |
| 기본 언어 | 한국어 (`ko-KR`) | 생성 완료 |
| 유형 | 앱 | 생성 완료 |
| 가격 | 무료 | 생성 완료. 무료 앱은 나중에 유료 다운로드 앱으로 전환할 수 없다는 경계를 이해하고 선택 |
| 정책·수출법 checkbox | 선택 완료 | 사용자 action-time 확인 후 생성 |
| `앱 만들기` | 실행 완료 | app ID `4976102237698469110`. Chrome에서 생성 후 대시보드 확인 |
| 자동 보호 | Console 기본 활성 상태로 보였으나 결정하지 않음 | release 영향 검토 후 사용자 결정 |
### 실행 체크
- [ ] account switcher에서 이메일·개발자명·계정 ID를 화면 캡처했다.
- [ ] Policy status에 계정 issue가 없는지 재확인했다.
- [ ] Android developer verification/패키지 등록 상태를 새 앱 생성 후 재확인했다.
- [x] Dashboard에서 production access가 disabled이고 12명·14일 closed-test 요구사항이 적용됨을 확인했다.
- [ ] 새 개인 계정에 기기 확인 요구가 나타나면 Android 10+의 root되지 않은 실제 기기에서 Play Console 모바일 앱으로 완료했다.
- [x] 사용자에게 정책·수출법 선언의 정확한 문구와 현재 사실을 보여주고 action-time 승인을 받았다.
- [x] 사용자 승인 후에만 두 checkbox와 `앱 만들기`를 실행했다.
- [x] 앱 생성 직후 package/name/default language/developer를 다시 기록했다: app ID `4976102237698469110`, `D3RO Voice`, `com.d3ro.voice`, `ko-KR`, `YUN CHAN`.
- [ ] 자동 보호의 장점·디버깅/비Play 배포 영향 검토 후 release별 설정을 기록했다. 기본값을 무심코 변경하지 않았다.
공식 기준: [앱 생성](https://support.google.com/googleplay/android-developer/answer/9859152?hl=en), [새 개인 계정 기기 확인](https://support.google.com/googleplay/android-developer/answer/14316361?hl=en).
## 2. 저장소·릴리스 원본 확정
- [ ] mobile productization 전체가 권위 있는 commit에 포함됐고 `origin/main` 또는 합의한 release branch에 push됐다.
- [ ] dirty/untracked 파일을 그대로 release source로 쓰지 않았다.
- [ ] unrelated 사용자 변경을 reset/clean/덮어쓰기 하지 않았다.
- [ ] release commit SHA와 tree hash를 기록했다: `<PLACEHOLDER_RELEASE_COMMIT_SHA>`.
- [ ] CI가 실제 연결된 remote에서 해당 SHA를 checkout했음을 증명했다.
- [ ] `.github/workflows/release.yml`의 Android job은 현재 GitHub Actions용이지만 저장소 remote는 GitLab/Forgejo다. 실행 가능한 GitHub private repository/runner가 실제로 연결되기 전에는 workflow 존재만으로 CI GREEN을 선언하지 않았다.
- [ ] 대안 CI를 사용한다면 release-only secrets, 동일 검증 스크립트, 제한된 AAB handoff와 provenance를 동등하게 구현했다.
- [ ] 과거 self-hosted `com.d3rovoice` APK, debug signer, TEST-DEMO APK를 Play artifact로 재사용하지 않았다.
- [x] desktop offline license는 mobile evidence key와 분리된 새 Ed25519 keypair를 사용하고, public SSOT `apps/desktop/resources/license/production-public.pem`의 keyId `5c52b765135ee2531c681b53cd4dc0e96fff8737f496c0b04ba8334fc81a887f`를 release metadata gate가 검증한다.
- [ ] desktop offline license를 운영한다면 외부 private key를 admin `ADMIN_LICENSE_PRIVATE_KEY` secret로 주입하고 실제 발급→desktop 활성화 roundtrip을 별도 검증했다. 이 항목은 Play AAB 서명/evidence key와 절대 공유하지 않는다.
## 3. Play App Signing·Firebase·App Links
### 인증서 경계
- [x] Console에서 Play app-signing certificate를 확인했다.
- [x] upload key `C:\Users\encep\.d3ro\release\d3ro-upload-key-20260821.p12` / alias `d3ro-upload-20260821`이 저장소 밖 user-only ACL로 보관되고 Credential Manager target이 존재하며 private-key readback이 GREEN임을 확인했다.
- [x] upload key certificate가 알려진 폐기 certificate와 다름을 확인했다. 실제 AAB signer 사용 증거는 아직 없다.
- [x] local upload certificate SHA-256: `4F:AC:69:24:82:1C:50:DA:AB:ED:76:49:32:A5:3C:48:6F:8C:6C:5F:34:B9:F1:8D:B9:20:AA:40:99:15:2B:54`
- [ ] 첫 AAB 업로드 후 Console에 표시된 upload certificate가 위 local certificate와 일치한다.
- [x] Play app-signing certificate SHA-256: `01:00:19:21:DB:4F:33:40:85:CE:21:E4:B8:DE:CC:BD:71:DA:87:67:C5:6E:3B:59:83:2A:A1:C8:29:EA:0D:AB`
- [ ] Play app-signing certificate SHA-1: `<PLACEHOLDER_ANDROID_PLAY_APP_SIGNING_CERT_SHA1>`
- [x] local upload certificate와 Play app-signing certificate의 SHA-256이 다름을 확인했다. 첫 AAB 후 Console upload certificate로 재확인한다.
- [ ] CI의 AAB signer 검증에는 upload SHA-256을 사용했다.
- [ ] Firebase OAuth, Google sign-in과 HTTPS App Links에는 **Play app-signing** SHA-1/SHA-256을 등록했다.
**현재 blocker:** `site/public`, `apps/web/public`, `apps/api-server/wwwroot`의 source `assetlinks.json` 3개는 확인된 Play app-signing SHA-256으로 교체했다. 그러나 live `https://d3ro.chanpaca.net/.well-known/assetlinks.json`은 아직 폐기된 과거 certificate를 반환하므로 배포와 public 재검증이 필수다. upload certificate는 `assetlinks.json`에 넣지 않는다.
코드 경계: `.github/workflows/release.yml``ANDROID_UPLOAD_CERT_SHA256``ANDROID_PLAY_APP_SIGNING_CERT_SHA256`을 분리하고, `scripts/ci/mobile-release-evidence-lib.mjs`와 App Links verifier가 폐기 certificate를 거부해야 한다.
### Firebase
- [x] Firebase Console `u/0`, `u/1`을 재확인했고 두 계정 모두 D3RO project가 없음을 확인했다.
- [ ] 사용자 action-time 승인 후 올바른 Google 계정에서 production Firebase project를 만들거나 명시적으로 선택했다: `<PLACEHOLDER_FIREBASE_PROJECT_ID>`.
- [ ] Android 앱 `com.d3ro.voice`를 등록했다.
- [ ] Play app-signing SHA-1/SHA-256을 Firebase Android 앱/OAuth client에 등록했다.
- [ ] production `google-services.json`을 CI secret으로 저장했다. 로컬/저장소에는 남기지 않았다.
- [ ] FCM sender/project가 Supabase production push 설정과 일치한다.
- [ ] Google OAuth redirect/deep link가 canonical `d3ro-voice://auth-callback`과 실제 Supabase/Firebase 설정에 일치하며, 폐기한 `d3ro://` scheme은 export되지 않는다.
- [ ] `https://d3ro.chanpaca.net/.well-known/assetlinks.json`이 Play app-signing fingerprint를 제공한다.
- [ ] Play 설치본에서 `adb shell pm get-app-links com.d3ro.voice`와 실제 invite URL 진입이 GREEN이다.
[Play App Signing 공식 설명](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en)에 따라 upload key와 app-signing key를 구분한다.
## 4. Production AAB 생성과 정적 검증
### 빌드 입력
- [x] 소스는 compile/target API 36, min API 24를 선언한다: `apps/mobile-rn/android/build.gradle:21-25`.
- [x] package는 `com.d3ro.voice`: `apps/mobile-rn/android/app/build.gradle:289-302`.
- [x] release cleartext traffic은 false이고 release ABI는 arm64-v8a: `build.gradle:372-386`.
- [x] release gate는 keystore, version, Firebase config, production AdMob app/banner/rewarded ID를 요구한다: `build.gradle:45-103,157-219`.
- [x] release identity를 `D3RO_VERSION_NAME=1.1.0`, `D3RO_VERSION_CODE=1010001`로 확정했다.
- [x] Ed25519 release-evidence keypair을 생성하고 private key를 저장소 밖 user-only ACL 경로에, public key를 `release/mobile-release-evidence-public.pem`에 보존했다. keyId `2797d3e63affd2348941bc2871b57e520c958f7f5da1a4bbe8aa46904a890b7f`, sign/verify roundtrip GREEN이다.
- [ ] upload/evidence private key·Firebase·AdMob secrets를 승인된 CI secret store에 주입하고 별도 복구 백업을 검증했다.
### 산출물·검증
- [ ] `:app:bundleRelease -PreactNativeArchitectures=arm64-v8a``app-release.aab`를 만들었다.
- [ ] AAB SHA-256: `<PLACEHOLDER_RELEASE_AAB_SHA256>`
- [ ] build source SHA: `<PLACEHOLDER_RELEASE_COMMIT_SHA>`
- [ ] package/versionName/versionCode/targetSdk/minSdk가 기대값과 일치한다.
- [ ] AAB signer가 upload certificate와 일치하고 debug/폐기 certificate가 아니다.
- [ ] embedded BuildConfig는 production Supabase/Firebase/AdMob ID이며 Google test AdMob publisher가 아니다.
- [ ] `android:debuggable=false`, `usesCleartextTraffic=false`를 확인했다.
- [ ] arm64 native library만 delivery되고 각 native dependency의 license를 보존했다.
- [ ] bundled Whisper model의 size/SHA가 gate 기대값과 일치한다.
- [ ] AAB, 검증 JSON, signed evidence, SHA256SUMS를 변경 불가능한 release record로 보관했다.
- [ ] AAB는 제한된 Play Console handoff artifact로만 제공하고 public release/site에 upload-key APK/AAB를 게시하지 않았다.
현재 작업공간의 `apps/mobile-rn/android/app/build/outputs/bundle/release/app-release.aab`는 존재하지 않는다. 기존 release APK나 E2E APK를 이 항목의 증거로 인정하지 않는다.
### 16 KB page size — 필수 blocker
D3RO Voice는 React Native/Whisper 등 native library를 포함하고 NDK r27.1을 지정하므로 source 설정만으로 호환을 가정하지 않는다.
- [ ] `bundletool dump config --bundle=<AAB>` 결과가 `PAGE_ALIGNMENT_16K`다.
- [ ] AAB에서 생성한 APK set의 모든 `.so` ELF segment alignment와 zip alignment를 공식 도구로 확인했다.
- [ ] Play pre-launch report에 16 KB page-size incompatibility가 없다.
- [ ] Android 15+ 16 KB emulator 또는 실제 16 KB 기기에서 clean install/cold start, 녹음, local Whisper transcription, Talk, 결제 SDK 초기화, 광고 SDK 초기화가 GREEN이다.
- [ ] native crash/UnsatisfiedLinkError가 없고 로그를 보존했다.
공식 기준: [16 KB page size 지원](https://developer.android.com/guide/practices/page-sizes). API 36은 2026-08-31부터 새 앱/업데이트 요구사항이며 현재 소스 설정은 이를 충족하지만 AAB 업로드 결과로 다시 확인한다: [Target API](https://support.google.com/googleplay/android-developer/answer/11926878?hl=en).
## 5. 스토어 등록정보와 자산
- [ ] [`01-store-listing.md`](01-store-listing.md)의 ko-KR/en-US 제목·설명을 실제 release 기능과 대조했다.
- [ ] category `Productivity`와 실제 제공되는 태그만 선택했다.
- [ ] 지원 이메일의 수신·응답을 확인했다.
- [ ] privacy/terms/delete URL이 익명 모바일 브라우저에서 HTTPS 200, 올바른 title/본문, no-login이다.
- [ ] root website가 유효한 지원 사이트가 아니면 website 필드를 비워 뒀다.
- [x] Play Console ko-KR 초안에 42자 짧은 설명, 1,213자 전체 설명, 512 아이콘, canonical E를 저장했다. 리뷰·트랙 제출은 진행하지 않았다.
- [x] 기본 Android robot launcher icon을 제거했다.
- [x] adaptive icon foreground/background/monochrome와 legacy density icon을 같은 canonical D3RO artwork로 만들었다.
- [x] Pixel Launcher emulator의 adaptive circle mask를 캡처했다: `docs/v3/play/assets/proof/d3ro-icon-pixel-launcher-20260824.png`, SHA-256 `7A2DA876FBFD54089B783A7FD45C2472476ED47DCEABA08AB305B7B01A18D430`.
- [ ] Play-signed release를 실제 Pixel/Samsung 등 OEM 기기에 설치해 icon scale·safe zone·monochrome를 캡처했다.
- [x] Play icon 512×512 PNG를 생성했다: `docs/v3/play/assets/d3ro-voice-play-icon-512.png`.
- [x] Play icon Console upload 후 alpha/모서리/안전 영역 경고가 없는지 확인했다.
- [x] feature graphic A-D 역사 후보와 imagegen canonical E의 원본·prompt·1024×500 제출본·해시 기록을 준비했다: `docs/v3/play/assets/feature-graphic/`.
- [x] 선택한 E를 Console preview에서 검증했다.
- [ ] phone screenshot 최소 2장, 권장 4장 이상을 실제 Play release 설치본에서 1080×1920급으로 캡처했다.
- [ ] 녹음, 전사 결과, Talk+신고, 회의/팀, 개인정보/삭제처럼 실제 핵심 기능을 보여준다.
- [ ] reviewer credential, 실사용자 이메일, 토큰, 실제 전사·회의 내용을 screenshot에 넣지 않았다.
- [ ] 로그인/초대 경계 E2E screenshot을 기능 대표 store asset으로 재사용하지 않았다.
## 6. 개인정보·Data safety·법적 문서
- [x] Settings에서 privacy, terms, account deletion URL을 연다: `apps/mobile-rn/src/screens/SettingsScreen.tsx:472-493`.
- [x] 가입 동의 영역에서 terms와 privacy를 각각 열 수 있다: `apps/mobile-rn/src/screens/SignUpScreen.tsx:326-369`.
- [x] 앱 내 계정 삭제 성공 후 계정 범위 로컬 데이터를 purge한다: `AccountScreen.tsx:410-455`, `account-local-data.ts:21-49`.
- [x] 공개 legal/deletion URL은 2026-08-24 mount 복구 후 외부 HTTPS 200이 확인됐다. 제출 직전 재검증한다.
- [ ] privacy 문서가 AdMob의 IP 기반 대략적 위치, 앱 상호작용, 진단, 광고/기기 ID와 광고·분석·사기 방지 목적을 명시한다.
- [ ] privacy/terms가 AI·UGC 신고 데이터, 금지 콘텐츠, 검토·조치, 보관·삭제를 반영한다.
- [ ] [`02-data-safety-draft.md`](02-data-safety-draft.md)의 모든 `검증 필요`를 release/provider 증거로 해소했다.
- [ ] Console Data safety preview와 공개 privacy 문서를 한 줄씩 대조했다.
- [ ] 계정 삭제 URL, 앱 내 경로와 제한 보관 항목을 Console에 정확히 답했다.
## 7. AI·UGC 정책 gate
- [x] `X-D3RO-Generation-Purpose`/`X-D3RO-Generation-Id`, 30일 generation receipt와 `talk_response|command_response|action_response` source contract가 구현됐다.
- [x] 신고 backend의 authenticated reporter, generation receipt/target allowlist, reason allowlist, comment/snapshot 길이 제한, rate limit, RLS, idempotency와 감사 상태가 구현됐다.
- [x] 일반 사용자의 `profiles.role` self-promotion과 `profiles.tier` self-assignment를 column ACL로 차단하고 public profile 필드 수정은 유지했다.
- [x] local SQL integration, HTTP Edge 15 assertions와 shared Deno 64 tests가 GREEN이다.
- [x] disposable local Supabase Gateway→Edge→DB Red E2E 650개가 GREEN이다: [`../e2e/RED_E2E_500_CONTENT_REPORT.md`](../e2e/RED_E2E_500_CONTENT_REPORT.md).
- [x] pending→reviewing→dismissed/actioned 흐름, 해결 후 180일 `evidence_expires_at`, account deletion identity unlink와 service-only purge RPC가 구현됐다.
- [ ] Talk와 모든 생성 문서 결과에서 인앱 신고 UI가 있다.
- [ ] AI 결과가 restricted content를 생성하지 않도록 provider safety/filter와 서버 정책을 검증했다.
- [ ] Play release 설치본에서 신고 sheet가 첨부될 AI 응답을 미리 보여주는지 확인했다. 제출 시 선택된 AI 응답 snapshot만 포함하며 음성·전체 대화·원래 prompt는 자동 복사하지 않는다.
- [ ] generation receipt에 raw prompt/전체 생성 결과가 없고 Edge/general audit log에 신고 snapshot 원문이 없는 것을 production log로 재확인했다.
- [x] moderation SLA·escalation·삭제/보관·purge·감사 runbook 초안을 만들었다: [`05-content-reporting-operations.md`](05-content-reporting-operations.md).
- [ ] 자동 purge scheduler, 실제 운영 담당과 실패 알림을 runbook에 연결했다.
- [x] 30일 receipt, 해결 전 evidence, 해결 후 180일, 계정 삭제 identity unlink와 purge를 privacy/terms 로컬 초안에 반영했다.
- [ ] privacy/terms 로컬 초안을 법률 검토 후 공개 배포하고 익명 모바일 브라우저에서 재검증했다.
- [ ] 운영 moderation 책임자·on-call을 실제 사람으로 지정했다.
- [ ] migration `20260824000029`, `content-report`/`llm-proxy` function과 모바일 신고 UI를 같은 production release에 배포했다.
- [ ] 신고 제출→관리 검토 원장 도달→상태 변경까지 production E2E가 GREEN이다.
- [ ] 초대 팀 외부로 데이터가 노출되지 않는 RLS/ACL을 검증했다.
- [ ] reviewer instructions에 신고 경로가 영어로 정확히 적혀 있다.
이 gate가 RED이면 생성형 AI 기능이 central feature인 현재 앱을 제출하지 않는다. [AI-Generated Content 정책](https://support.google.com/googleplay/android-developer/answer/13985936?hl=en).
## 8. Google Play Billing과 merchant
- [x] 코드 상품 ID는 `d3ro_voice_pro_monthly`, `d3ro_voice_pro_plus_monthly`: `apps/mobile-rn/src/lib/billing-context.tsx:32-35`.
- [x] 현재 local resolved dependency evidence는 Play Billing Library 9.1.0이다. production Gradle dependency report로 재확인한다.
- [x] PBL 9는 2028-08-31까지의 현재 신규 앱/업데이트 지원 창에 있으며, 2026-08-31에 종료되는 PBL 7 경계를 넘는다. 최종 AAB manifest의 `com.google.android.play.billingclient.version`으로 다시 증명한다: [PBL deprecation timeline](https://developer.android.com/google/play/billing/deprecation-faq).
- [x] 구매는 서버 `iap-verify` 검증과 entitlement snapshot 확인 후 성공 처리하는 경계가 있다.
- [ ] Google Play payments/merchant profile을 올바른 법적 개인·국가·세금 정보로 완료했다: `<PLACEHOLDER_MERCHANT_PROFILE_ID>`.
- [ ] 두 subscription과 base plan/offer를 Console에 정확한 ID로 만들었다.
- [ ] 가격, 결제 주기, 자동 갱신, 무료 체험 여부를 ko/en 구매 화면과 일치시켰다. site 문구만 보고 trial을 생성하지 않았다.
- [ ] Google Play Developer API service account와 최소 권한을 설정했다: `<PLACEHOLDER_PLAY_SERVICE_ACCOUNT>`.
- [ ] RTDN Pub/Sub push 인증과 production Edge endpoint를 설정했다.
- [ ] license tester 계정으로 신규 구매, pending→purchased, 취소, grace/on-hold, upgrade/downgrade, restore, refund/voided purchase를 확인했다.
- [ ] purchase token replay/cross-account/package mismatch가 거부된다.
- [ ] 계정 삭제가 Play 구독을 자동 취소하지 않음을 앱·웹에서 명확히 안내한다.
- [ ] reviewer Pro+ 계정은 실제 결제를 요구하지 않고 review 기간 내 유지된다.
## 9. AdMob·UMP·SSV
- [x] `release/android-release-identity.json`과 live Console의 AdMob identity를 대조했다: app `ca-app-pub-1039714767792854~6427959892`, banner `ca-app-pub-1039714767792854/9840591290`, rewarded `ca-app-pub-1039714767792854/2255790918`.
- [x] production SSV URL과 rewarded `50 cloud_ai_tokens` 계약을 저장했다.
- [ ] AdMob의 `검토 필요`·`광고 게재 제한` 상태를 해소했다.
- [ ] AdMob 지급 프로필을 완료했다: `<PLACEHOLDER_ADMOB_PAYMENT_PROFILE_STATUS>`.
- [ ] Play store 등록정보와 AdMob app을 연결하고 app review/approval을 완료했다.
- [ ] UMP privacy message를 대상 지역에 publish하고, consent granted/denied/not-required/privacy-options-required를 각각 확인했다.
- [ ] Play internal 설치본을 AdMob test device로 등록했다.
- [ ] 실제 Google-signed rewarded callback→durable receipt→claim→`+50 cloud_ai_tokens`를 test device에서 E2E 검증했다.
- [ ] 중복 transaction, unknown user, cap/cooldown/ineligible, account deletion unlink와 retry가 의도대로 동작한다.
- [ ] Pro/Pro+에서 SDK ad request가 발생하지 않는다.
- [ ] production 광고를 개발자/테스터가 클릭하지 않았다.
AdMob 콘솔 준비를 실제 광고 게재 승인·수입·Play release 완료로 해석하지 않는다.
## 10. App content 선언과 reviewer 자료
- [ ] Ads=`Yes`, Advertising ID=`Yes`를 final merged manifest와 맞춰 저장했다.
- [ ] App access에 Free와 Pro+ reusable 계정을 영어로 등록했다.
- [ ] 두 계정 모두 OTP/2FA/CAPTCHA/지역 제한 없이 clean install에서 로그인된다.
- [ ] Target audience는 18+ only, children appeal은 No로 제품 경계와 일치한다.
- [ ] IARC에 ads, digital purchase, generative AI, invited-team UGC/interaction을 숨김없이 답했다.
- [ ] Financial features는 내부 quota가 비양도·비환금임을 확인한 뒤 `My app doesn't provide any financial features`로 저장했다.
- [ ] account deletion 답변과 공개 URL을 저장했다.
- [ ] microphone FGS type/use case/설명/지연 영향과 `<PLACEHOLDER_FGS_DEMO_URL>`을 입력했다.
- [ ] FGS 영상 URL을 로그아웃/익명 창에서 열 수 있다.
- [ ] 모든 placeholder를 제거했다.
- [ ] [`03-app-content-draft.md`](03-app-content-draft.md)와 Console preview를 최종 대조했다.
## 11. Internal track — Play-signed 실기기 E2E
- [ ] AAB를 internal track에 upload하고 processing/error/warning을 모두 검토했다.
- [ ] App Bundle Explorer에서 delivered APK의 package/version/target/ABI/permission을 확인했다.
- [ ] Play app-signing certificate가 기대값과 일치한다.
- [ ] Play internal opt-in link로 실제 기기에 clean install했다. sideload APK로 대체하지 않았다.
- [ ] Android 10+ 일반 4 KB 기기와 Android 15+ 16 KB 환경에서 검증했다.
- [ ] fresh onboarding→email login→logout→다른 계정 login의 privacy boundary가 GREEN이다.
- [ ] email signup, 약관/privacy 링크, OAuth redirect를 검증했다.
- [ ] 마이크 허용/거부, notification 허용/거부, FGS background/lock/pause/resume/stop/discard, 통화/오디오 focus interruption을 검증했다.
- [ ] cloud STT 정상/timeout/provider 장애→local fallback, offline queue/resume/cancel, low storage/process kill을 검증했다.
- [ ] MP3/M4A/AAC/OGG/FLAC/WAV/WebM/MP4 유효 파일과 invalid/oversize/mime mismatch를 검증했다.
- [ ] Talk streaming, TTS, AI 신고와 restricted prompt 처리 흐름을 검증했다.
- [ ] team invite warm/cold HTTPS App Link, 역할, 제거/탈퇴, RLS를 검증했다.
- [ ] FCM 등록·수신·logout detach·계정 삭제 detach를 검증했다.
- [ ] Free ads/UMP/rewarded SSV와 Pro+ no-ad를 검증했다.
- [ ] Play subscription purchase/restore/cancel 상태를 검증했다.
- [ ] export/restore와 계정 삭제 후 서버·로컬 데이터 제거를 검증했다.
- [ ] 앱 업데이트에서 데이터/세션/구독을 보존하고 signer lineage가 일치한다.
- [ ] Play pre-launch report의 crash, ANR, accessibility, security, permission, 16 KB 문제를 처리했다.
## 12. Closed test와 production access
개인 개발자 계정이 2023-11-13 이후 생성됐다면 production 전에 최소 12명이 closed test에 **연속 14일** opt-in 상태여야 한다. tester가 중간에 opt-out하면 연속 기간이 다시 계산된다.
- [x] Console Dashboard에서 12명·연속 14일 요구사항과 production access disabled 상태를 확인했다.
- [ ] 적용 시 최소 12명의 실제 tester를 모집하고 개인정보·NDA·피드백 경로를 안내했다.
- [ ] tester 목록과 opt-in 시각을 보존했다.
- [ ] 14일 동안 crash-free install만 세지 않고 녹음, 전사, AI, 팀, 광고/결제, 삭제 기능을 실제 사용하게 했다.
- [ ] 버그·사용성·배터리·권한·광고·AI 안전 피드백과 수정 내역을 보존했다.
- [ ] production access 질문에 실제 tester 참여/피드백/수정 내용만 답했다.
- [ ] 최소 12명이 신청 순간까지 연속 opt-in 상태임을 Console에서 확인했다.
- [ ] production access 승인 전 open testing/production을 사용할 수 있다고 가정하지 않았다.
공식 기준: [새 개인 계정 테스트 요구사항](https://support.google.com/googleplay/android-developer/answer/14151465?hl=en-GB).
## 13. Production 제출·점진 배포
- [ ] internal/closed에서 검증한 **동일 AAB SHA** 또는 정당한 수정 후 새로 전부 검증한 AAB를 사용했다.
- [ ] release notes ko/en을 작성했다.
- [ ] country/region과 가격·세금·정책 적용 범위를 검토했다.
- [ ] device catalog 제외가 기능/SDK 사실에 근거하며 불필요하게 넓지 않다.
- [ ] 모든 Console warning과 policy declaration을 검토했다.
- [ ] 사용자 본인이 production 제출 버튼과 법적 선언을 action-time 확인했다.
- [ ] 첫 production은 staged rollout으로 시작하고 비율·중단 기준을 기록했다.
- [ ] Android vitals crash/ANR, FGS/배터리, 결제 RTDN, SSV, 신고 queue, auth/STT/AI provider error를 모니터링한다.
- [ ] rollback은 versionCode를 되돌리는 것이 아니라 수정된 더 높은 versionCode로 배포한다.
- [ ] 스토어 페이지·설치·로그인·핵심 흐름을 외부 사용자 관점에서 재검증했다.
## 14. 최종 evidence pack
다음 자료가 하나의 release record에서 같은 version/SHA를 가리켜야 한다.
- [ ] release commit SHA/tree, CI run URL과 runner identity
- [ ] AAB SHA-256, versionName/versionCode, package, target/min API
- [ ] upload certificate, Play app-signing certificate SHA-1/SHA-256
- [ ] signed build evidence, dependency report, merged manifest, bundletool/16 KB 결과
- [ ] Play Console app ID, App Signing, App Bundle Explorer, track/review 상태 캡처
- [ ] Firebase app/fingerprint/FCM/App Links 증거
- [ ] Data safety/App content/IARC/FGS preview와 공개 legal page 캡처
- [ ] reviewer 계정 로그인 결과(비밀번호 제외)
- [ ] 실제 Play 설치본의 device/OS/build 정보와 기능 E2E 로그·화면
- [ ] Billing/RTDN, AdMob UMP/SSV, AI report moderation의 redacted production 증거
- [ ] closed-test tester 수·연속 기간·피드백과 production access 결과
- [ ] staged rollout과 Android vitals 모니터링 기록
## 외부 placeholder 레지스트리
| Placeholder | 실제 값 소유 위치 | 완료 조건 |
| -------------------------------------------- | ---------------------------- | ------------------------------------------------------- |
| `<PLACEHOLDER_RELEASE_COMMIT_SHA>` | Git remote/CI | 권위 있는 release source SHA |
| versionName `1.1.0` / versionCode `1010001` | release SSOT | 확정. 실제 AAB·Console metadata 대조는 대기 |
| `<PLACEHOLDER_RELEASE_AAB_SHA256>` | restricted artifact/evidence | 실제 production AAB hash |
| upload SHA-256 `4F:AC:69:24:...:15:2B:54` | external key/Play Console | local 값 확정. AAB signer·Console 표시와 일치 대기 |
| evidence keyId `2797d3e6...4a890b7f` | release identity/CI secret | public·roundtrip 확인. private CI secret·복구 백업 대기 |
| Play app-signing SHA-1 | Play Console App integrity | SHA-256은 확인. SHA-1·Firebase 대조는 대기 |
| `<PLACEHOLDER_FIREBASE_PROJECT_ID>` | Firebase Console | 올바른 계정·package 등록 |
| `<PLACEHOLDER_PLAY_SERVICE_ACCOUNT>` | Google Cloud/Play Console | 최소 권한, RTDN/API GREEN |
| `<PLACEHOLDER_MERCHANT_PROFILE_ID>` | Google payments/Play Console | 상품 판매 가능, 법적·세금 정보 완료 |
| `<PLACEHOLDER_ADMOB_PAYMENT_PROFILE_STATUS>` | AdMob/Google payments | 지급 profile와 검토 완료 |
| reviewer credentials | Play Console App access만 | 24시간 재사용 가능, no OTP |
| `<PLACEHOLDER_FGS_DEMO_URL>` | unlisted video storage | 익명 접근 가능, 실제 release 실기기 영상 |
| `<PLACEHOLDER_REPORT_TEST_EVIDENCE>` | redacted production evidence | 신고→운영 원장 E2E GREEN |