# Pro Entitlement 운영 가이드

## 목적

이 문서는 Android `Pro 다계정 영구 잠금 해제`의 Google Play 상품과 구매 검증 서비스를
운영·복구하는 단일 기준이다. 구현 설계는
`docs/superpowers/specs/2026-08-01-android-pro-entitlement-design.md`를 따른다.

이 문서에는 실제 자격 증명, secret 값, 구매 토큰, Google 계정 정보, Cloud project ID,
배포 URL, 로컬 경로를 기록하지 않는다. 서명키 값은 Google Secret Manager에만 두고 리소스
식별자와 fingerprint는 승인된 로컬 운영 인벤토리에만 둔다.

## 운영 구성

- Google Play 일회성 상품: `pro_multi_account`
- 상품 유형: 비소모성 일회성 구매
- 한국 가격 기준: `₩9,900`
- Android package: release manifest의 확정 package를 기준으로 자동 검증
- 검증 서비스: `apps/entitlement-verifier`, Google Cloud Run
- 구매 상태 저장: Firestore, 구매 토큰 SHA-256 해시 기반
- 구매 알림: Google Play RTDN → Pub/Sub push → 검증 서비스
- 비밀 저장: entitlement 서명 개인키만 Google Secret Manager에 저장
- 공개키 배포: 로컬 백엔드 release asset에 verifier 공개키 포함

## Secret과 권한

운영자는 entitlement Ed25519 서명 개인키 secret의 이름과 버전만 배포 설정에서 참조한다.
값은 소스, CI 로그, 문서, Android asset, PWA bundle, 로컬 backend config에 넣지 않는다.
RTDN audience와 호출 서비스 계정은 비밀이 아닌 엄격한 allowlist 설정으로 관리한다.

Google 서비스 계정 키 파일은 만들지 않는다. 전용 Cloud Run runtime 서비스 계정을 Play Console에
연결하고 Application Default Credentials로만 구매 API를 호출한다. 이 계정에는 구매 조회와
acknowledgement에 필요한 최소 권한만 부여한다. Cloud Run runtime 계정만 entitlement signing
secret의 현재 버전을 읽을 수 있어야 한다. 운영자 개인 계정의 상시 secret 읽기 권한은 두지 않는다.

## 최초 설정 절차

1. Play Console에서 `pro_multi_account` 비소모성 일회성 상품을 생성한다.
2. 한국 가격을 `₩9,900`으로 지정하고 출시 국가별 현지 가격을 검토한다.
3. Google Cloud의 Play Developer API를 활성화한다.
4. 최소 권한 서비스 계정을 Play Console 개발자 계정과 연결한다.
5. entitlement Ed25519 키쌍을 안전한 운영 환경에서 생성한다.
6. 개인키만 Secret Manager에 저장하고 Google 서비스 계정 키 파일은 생성하지 않는다.
7. 공개키를 release backend asset에 고정하고 fingerprint를 운영 기록과 대조한다.
8. Firestore의 purchase 상태 collection과 최소 보존 정책을 적용한다.
9. RTDN Pub/Sub topic과 인증된 Cloud Run push subscription을 연결한다.
10. Cloud Run이 요청 본문과 인증 헤더를 로그에 남기지 않는지 fixture로 확인한다.

설정이 끝나도 상품을 바로 공개하지 않는다. 내부 테스트 트랙 검증을 모두 통과한 뒤 활성화한다.

## 배포 전 점검

- Cloud Run release가 승인된 commit과 image digest를 사용함
- package와 `pro_multi_account`가 서버 allowlist와 일치함
- verifier 공개키 fingerprint가 Android/백엔드 release와 일치함
- 서비스 계정 키 파일이 없고 서명키가 image·환경변수 평문·로그에 없음
- Firestore와 Pub/Sub 권한이 runtime 서비스 계정으로 제한됨
- rate limit, body size, JSON content type, request timeout 제한이 적용됨
- health endpoint가 구매·secret 내용을 반환하지 않음
- staging 테스트 구매가 production entitlement를 만들지 않음

### 로컬 출시 게이트

Cloud 리소스를 만들거나 revision을 배포하기 전에 저장소 루트에서 다음 검사를 통과시킨다.

```powershell
npm.cmd --workspace apps/entitlement-verifier test
node --test tools/verifier-release.test.mjs
npm.cmd test
npm.cmd --workspace apps/pwa exec vite build
git diff --check
```

`tools/verifier-release.test.mjs`는 고정 Node image, 비루트 실행, production dependency 설치,
health check, 최소 container context를 검사한다. 또한 개인키·서비스 계정 JSON·구매 fixture·원문 오류
로깅·package/product 불일치를 거부하고, fake Google 구매 조회 → acknowledgement → 인증서 발급 →
중복 복원 → RTDN 무효화 → 이후 발급 거절 순서를 로컬에서 검증한다.

승인된 container 빌드 환경에서는 `apps/entitlement-verifier`를 context로 image를 실제 빌드하고
승인 commit과 image digest를 대조한다. Docker가 없는 개발 PC의 정적 검사는 실제 image build를
대체하지 않는다. image build, Cloud Run 배포, Play Console 연결, Firestore·Pub/Sub·Scheduler 생성,
Secret Manager 업로드는 각각 명시적인 운영자 승인 뒤에만 수행한다.

환경 변수 이름과 최소 IAM 경계는 `apps/entitlement-verifier/README.md`를 따른다. 실제 값, project ID,
서비스 URL, 계정, secret 식별자는 승인된 로컬 운영 인벤토리에서만 관리한다.

## 출시 전 실제 구매 검증

Google Play license tester와 내부 테스트 트랙을 사용해 다음 순서를 한 번 끝까지 수행한다.

1. 무료 새 설치에서 첫 Codex 실행 계정을 등록한다.
2. 두 번째 계정 추가 시 Play 상품명과 현지 가격이 표시되는지 확인한다.
3. 테스트 구매 후 검증과 acknowledgement가 성공하는지 확인한다.
4. 같은 Play 계정의 다른 Android 기기에서 구매를 복원한다.
5. Pro에서 실행 계정 다섯 개까지 등록하고 여섯 번째를 거절하는지 확인한다.
6. 결제 취소와 pending 상태가 Pro를 부여하지 않는지 확인한다.
7. Play Console에서 환불한 뒤 새 증명 발급이 거절되는지 확인한다.
8. 검증 서비스 장애 중 기존 증명과 유예가 유지되는지 확인한다.
9. Android 앱, 로컬 backend, Cloud Logging에 구매 토큰이 남지 않는지 확인한다.

각 결과는 날짜, release version, tester 종류, 성공·실패만 출시 증거에 기록한다. 주문 ID와 구매
토큰은 기록하지 않는다.

## 약관·개인정보·사이트 동기화

결제가 들어간 release마다 다음 정합성을 확인한다.

- 앱 내 Legal/Support 링크와 Play Console이 동일한 `/privacy`, `/terms`, `/support`,
  `/delete-account` 공개 URL을 사용함
- 이용약관이 무료 1계정, Pro 5계정, 비소모성 일회성 구매, 구매 복원, Google Play 결제,
  환불 문의와 적용 법률상 권리를 설명함
- 개인정보처리방침이 구매 토큰 처리, 토큰 해시·설치 공개키 지문 저장, 처리 목적, 보존·삭제,
  Google Play와 Google Cloud 처리 범위를 설명함
- Store Listing과 Pro 구매 시트가 구독이나 Codex 구독 포함을 암시하지 않음
- Data Safety의 `Purchase history` 및 관련 진단·식별 정보 판단이 실제 Billing SDK와 검증 서비스
  동작, 개인정보처리방침과 일치함
- 지원 페이지가 Google Play 환불 요청과 `coderemote@makecdev.com` 문의 경로를 제공함
- 한국어와 영어 문서가 같은 제품·데이터·환불 범위를 설명함

정책 source는 `distribution/TERMS.txt`, `distribution/PRIVACY.txt`, `website/content.mjs`,
`docs/STORE_LISTING.md`, `docs/PLAY_DATA_SAFETY.md`다. generated website와 release 패키지
문서를 직접 수정하지 않는다.

공개 사이트 배포는 별도 운영자 승인 후 수행한다. 배포 뒤 앱 내 링크와 Play Console에서 실제
페이지를 열어 상태 코드, TLS, 본문 version을 확인한다. 출시 국가가 바뀌거나 환불 정책을 바꾸면
새 법률 검토 없이 기존 문구를 재사용하지 않는다.

## 공개 문구 동기화 매트릭스

각 release와 정책 변경 때 아래 행을 왼쪽에서 오른쪽으로 다시 확인한다. `distribution` 문서가
구현된 lifecycle과 privacy의 권위 source이며, website와 Store Listing은 그 공개 projection이다.
Play Console 값은 저장소 문서를 복사했다고 완료 처리하지 말고 실제 Console 화면과 최종 AAB를
대조한다.

| 공개 주장 | 권위 source | Play Console field | 공개 projection | 재확인 증거 |
| --- | --- | --- | --- | --- |
| 무료/Pro 계정 한도 | `distribution/TERMS.txt` | Main store listing, in-app product description | `website/content.mjs`, `docs/STORE_LISTING.md` | 무료 실행 계정 1개, Pro 최대 5개, `historyOnly` 제외가 앱 UI·제품 설명과 일치 |
| 일회성 비소모성 구매와 현지 가격 | `distribution/TERMS.txt` | In-app products > `pro_multi_account`; store listing | `website/content.mjs`, `docs/STORE_LISTING.md` | 상품 유형이 managed/non-consumable이고 실제 판매 국가별 가격은 Console이 표시하며 고정 가격 문구가 없음 |
| 구매 복원 | `distribution/TERMS.txt` | In-app product/review notes and app access instructions | `website/content.mjs`, `docs/STORE_LISTING.md` | 소유 Play 계정으로 foreground 복원 성공; 자동 주기 재검증 문구 없음 |
| 환불과 지원 | `distribution/TERMS.txt` | Store settings > contact details; refund policy/support URL | `website/content.mjs`, `docs/STORE_LISTING.md` | Google Play 절차, 적용 법률상 권리, `coderemote@makecdev.com`과 `/support`가 일치 |
| 검증 데이터와 처리자 | `distribution/PRIVACY.txt` | App content > Data safety > Data collected/shared, purposes | `website/content.mjs`, `docs/PLAY_DATA_SAFETY.md` | Google Play, Cloud Run, Firestore, Pub/Sub, Billing·ML Kit의 최종 artifact 데이터 흐름 확인 |
| 보존과 삭제 | `distribution/PRIVACY.txt` | App content > Data safety > Data deletion; Data deletion URL | `website/content.mjs`, `docs/PLAY_DATA_SAFETY.md` | 원본 토큰 미보존, 활성 구매 유효 기간, 무효화 180일, Pub/Sub 30일, 지원 요청 경로 확인 |
| 검증 장애와 서비스 종료 | `distribution/TERMS.txt` | In-app product/review notes and support content | `website/content.mjs`, `docs/STORE_LISTING.md` | 증명 30일, 복원 재검증, 기존 구매자 7일 유예, 종료 시 Pro 보존 또는 다계정 무료 전환 확인 |

매트릭스 확인 결과는 release 증거에 날짜와 pass/fail만 기록한다. Play Console project 식별자,
구매 토큰, 주문 ID, 계정 주소, 서명 자료, 로컬 경로는 기록하지 않는다.

## 정기 운영 점검

매월 한 번 다음 항목을 확인한다.

- Cloud Run health, 오류율, p95 응답 시간의 급격한 변화
- Google Play Developer API quota와 권한 오류
- RTDN Pub/Sub dead-letter와 delivery failure
- Firestore write/read 오류와 비정상적인 토큰 해시 반복
- Secret Manager의 활성 key version과 접근 감사 로그
- verifier 공개키 fingerprint와 현재 release 일치
- 테스트 구매 1건의 구매·복원·acknowledgement
- 개인정보·구매 토큰이 로그에 포함되지 않았는지 표본 검사
- 앱·사이트·Play Console 약관, 개인정보, 상품, 환불 문구의 정합성

경보에는 package, 상태 코드, 비식별 request ID만 포함한다. 구매 토큰, 주문 ID, 사용자 이메일은
경보 메시지에 넣지 않는다.

## 장애 대응

### 검증 서비스 장애

1. 신규 구매 검증을 일시 중단하고 Play 결제 자체를 반복 실행하지 않도록 안내한다.
2. 기존 Pro의 30일 증명과 7일 유예가 유지되는지 확인한다.
3. Cloud Run revision, Secret 접근, Google API, Firestore, Pub/Sub 순으로 확인한다.
4. 마지막 정상 revision으로 되돌릴 때 signing key와 공개키 호환성을 먼저 확인한다.
5. 복구 후 미완료 구매는 앱 foreground의 구매 복원으로 다시 검증한다.

### Google API 또는 runtime identity 오류

1. ADC access token이나 인증 헤더를 로그로 출력하지 않는다.
2. Play Console 연결, API 활성화, Cloud Run runtime 서비스 계정과 최소 IAM 권한을 확인한다.
3. runtime identity가 침해됐다고 의심되면 revision을 중지하고 새 runtime 서비스 계정으로
   최소 권한을 재구성한 뒤 이전 계정의 권한을 폐기한다. 키 파일을 발급해 우회하지 않는다.
4. 기존 entitlement signing key는 runtime identity와 별개이므로 함께 교체하지 않는다.

### Entitlement 서명키 노출

1. 영향받은 private key version을 즉시 비활성화한다.
2. 새 Ed25519 키쌍을 만들고 Secret Manager에 저장한다.
3. 이전·신규 공개키를 함께 신뢰하는 backend 긴급 release를 먼저 배포한다.
4. verifier를 신규 개인키로 전환한다.
5. 기존 증명 만료와 7일 유예가 끝난 뒤 이전 공개키 신뢰를 제거한다.
6. 노출 기간, 영향 release, 회수 완료만 보안 기록에 남긴다.

### 잘못된 무료 전환

1. 계정이나 채팅을 삭제하지 않았는지 먼저 확인한다.
2. Play 구매 복원을 실행하고 verifier의 비식별 request ID로 실패 지점을 찾는다.
3. Google 구매는 유효하지만 서비스 오류라면 유예 정책을 보존한 채 수정 release를 낸다.
4. 수동으로 임의 Pro flag를 넣거나 구매 토큰을 운영자에게 보내게 하지 않는다.

### 환불과 무효 구매

RTDN과 다음 재검증에서 토큰 해시 상태를 무효로 바꾼다. 새 증명은 발급하지 않는다. 현재 증명과
유예가 끝나면 무료 한도로 복귀하되 계정, 인증 파일, 채팅은 삭제하지 않는다.

## 백업과 복구

- Firestore에는 최소 구매 상태만 있으므로 매일 관리형 백업과 보존 기간을 적용한다.
- signing key는 Secret Manager version과 별도 오프라인 복구 절차로 이중 보관한다.
- 복구 시험은 production 구매 토큰을 복사하지 않고 license tester 데이터로 수행한다.
- Firestore를 잃어도 사용자가 Play 구매 복원을 실행하면 Google API로 상태를 재검증할 수 있어야
  한다.
- 공개키 fingerprint, secret version, Cloud Run image digest의 대응표는 승인된 비공개 운영
  인벤토리에 기록한다.

## 서비스 종료 절차

검증 서비스를 종료해야 하면 구매자를 먼저 보호한다.

1. Play Console에서 신규 `pro_multi_account` 판매를 중단한다.
2. 기존 구매자의 Pro를 보존하거나 모든 사용자에게 다계정을 무료화하는 signed 앱 업데이트를
   배포한다.
3. 해당 업데이트의 보급률과 복원 성공을 확인한다.
4. 남아 있는 환불·지원 창구와 데이터 보존 의무를 처리한다.
5. 그 뒤 RTDN, Cloud Run, Firestore, 서비스 계정, secret 순으로 폐기한다.

검증 서버를 먼저 끄거나 기존 구매자에게 재구매를 요구하지 않는다.

## 운영자가 나중에 다시 시작할 때

1. 이 문서의 `정기 운영 점검`을 실행한다.
2. Play Console에서 상품 상태와 가격을 확인한다.
3. Cloud Run image digest와 verifier 공개키 fingerprint를 release와 대조한다.
4. license tester로 구매 복원 한 건을 수행한다.
5. 실패하면 `장애 대응`의 해당 절차만 따른다.

## 공식 정책 확인 링크

운영 재개, 출시 국가 변경, 환불 정책 변경, Billing SDK 업데이트 때 다음 최신 공식 문서를 다시
확인한다. 과거에 저장한 요약보다 현재 Google 문서를 우선한다.

- 결제 정책: https://support.google.com/googleplay/android-developer/answer/9858738
- 사용자 데이터·개인정보처리방침: https://support.google.com/googleplay/android-developer/answer/10144311
- Data Safety 작성 기준: https://support.google.com/googleplay/android-developer/answer/10787469
- 주문·환불 운영: https://support.google.com/googleplay/android-developer/answer/2741495
- 사용자 환불 요청 경로: https://support.google.com/googleplay/answer/15574897
- Play Billing 백엔드 통합: https://developer.android.com/google/play/billing/backend
- 구매 검증과 부정 사용 방지: https://developer.android.com/google/play/billing/security
