feat(release): prepare 1.1.0 candidate
126
docs/v3/play/01-store-listing.md
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# 스토어 등록정보 초안
|
||||
|
||||
## 공통 메타데이터
|
||||
|
||||
| 필드 | 입력안 | 상태·근거 |
|
||||
| --------- | -------------------------- | ------------------------------------------------------------------------ |
|
||||
| 앱 이름 | `D3RO Voice` | **후보**, 10자. 앱 label과 제품명에 일치 |
|
||||
| 앱/게임 | 앱 | **확정**, 생산성 앱 |
|
||||
| 무료/유료 | 무료 | **후보**, Free 요금제 + 선택형 구독·광고 모델 |
|
||||
| 기본 언어 | 한국어 (`ko-KR`) | Play 생성 양식 준비값 |
|
||||
| 카테고리 | 생산성 (`Productivity`) | **후보** |
|
||||
| 태그 | 음성 녹음, 전사, 메모/노트 | **검증 필요**, Console이 제공하는 실제 태그 중 기능과 일치하는 것만 선택 |
|
||||
| 패키지 | `com.d3ro.voice` | `apps/mobile-rn/android/app/build.gradle:289-302` |
|
||||
|
||||
Play Console이 허용하는 스토어 제목은 30자, 짧은 설명은 80자, 전체 설명은 4,000자 이하여야 한다. 아래 문자 수는 줄바꿈을 제외한 해당 필드 원문 기준이며, 입력 후 Console의 실제 카운터를 다시 확인한다.
|
||||
|
||||
## `ko-KR`
|
||||
|
||||
### 앱 이름
|
||||
|
||||
```text
|
||||
D3RO Voice
|
||||
```
|
||||
|
||||
### 짧은 설명 — 42자
|
||||
|
||||
```text
|
||||
녹음·전사·AI 문서화·팀 협업까지, 음성을 바로 활용할 수 있는 기록으로.
|
||||
```
|
||||
|
||||
### 전체 설명
|
||||
|
||||
```text
|
||||
말한 내용을 놓치지 않고, 다시 활용할 수 있는 기록으로 바꿔보세요.
|
||||
|
||||
D3RO Voice는 음성 녹음과 미디어 가져오기, 전사, AI 보조 문서화, 메모와 팀 협업을 한곳에서 이어주는 생산성 앱입니다.
|
||||
|
||||
주요 기능
|
||||
• 마이크 녹음: 지속 알림에서 녹음 상태를 확인하고 일시정지, 재개, 중지 또는 폐기할 수 있습니다.
|
||||
• 음성·미디어 가져오기: 앱이 검증할 수 있는 오디오 형식과 MP4 파일을 선택해 처리할 수 있습니다.
|
||||
• 전사 경로: 인증된 클라우드 전사를 사용하고, 지원되는 Android 환경에서는 제공자 장애 시 기기 내 전사를 대체 경로로 사용합니다.
|
||||
• 회의와 기록: 전사문, 회의록, 메모, 템플릿과 생성 문서를 계정에 정리합니다.
|
||||
• AI 보조: 대화형 AI와 문서 생성 기능으로 전사 내용을 요약하고 다음 작업을 구체화할 수 있습니다.
|
||||
• 팀 협업: 초대한 팀원과 팀 회의 및 관련 기록을 공유하고 역할을 관리할 수 있습니다.
|
||||
• 데이터 관리: 계정 데이터 내보내기·복원, 기기 관리와 계정 삭제 경로를 제공합니다.
|
||||
|
||||
요금제와 광고
|
||||
Free 요금제에는 Google AdMob 배너 광고가 표시될 수 있습니다. 사용자가 선택해 보상형 광고를 완료하면 서버 검증 후 앱 내부 AI 사용량 크레딧을 받을 수 있습니다. 선택형 Google Play 구독은 추가 사용량과 광고 없는 이용 환경을 제공할 수 있습니다. 실제 가격과 갱신 조건은 구매 화면과 Google Play 결제 창에 표시됩니다.
|
||||
|
||||
개인정보와 책임 있는 사용
|
||||
마이크 권한은 녹음을 시작할 때 요청합니다. 음성·텍스트는 사용자가 클라우드 기능을 실행한 경우 필요한 범위에서 처리되며, 광고 요청은 동의 및 개인정보 옵션을 확인한 뒤 시작합니다. 개인정보처리방침과 계정 삭제 경로는 앱 설정에서 언제든 확인할 수 있습니다.
|
||||
|
||||
자동 전사와 AI 생성 결과에는 오류가 있을 수 있습니다. 중요한 사실과 결정을 직접 검토하고, 의료·법률·재무 또는 안전에 중대한 전문 조언으로 사용하지 마세요. 타인의 음성을 녹음하거나 콘텐츠를 공유할 때는 관련 법률과 필요한 동의를 지켜야 합니다.
|
||||
```
|
||||
|
||||
## `en-US`
|
||||
|
||||
### App name
|
||||
|
||||
```text
|
||||
D3RO Voice
|
||||
```
|
||||
|
||||
### Short description — 76 characters
|
||||
|
||||
```text
|
||||
Record, transcribe, and turn conversations into practical AI-assisted notes.
|
||||
```
|
||||
|
||||
### Full description
|
||||
|
||||
```text
|
||||
Turn spoken ideas into records you can review, organize, and use again.
|
||||
|
||||
D3RO Voice brings microphone recording, supported media import, transcription, AI-assisted documents, notes, and team collaboration into one productivity app.
|
||||
|
||||
Key features
|
||||
• Microphone recording: See an ongoing notification and pause, resume, stop, or discard a recording.
|
||||
• Supported media import: Select audio formats and MP4 files that the app can independently validate and process.
|
||||
• Transcription paths: Use authenticated cloud transcription, with on-device transcription as a fallback on supported Android environments when the cloud provider is unavailable.
|
||||
• Meetings and records: Organize transcripts, meeting notes, memos, templates, and generated documents in your account.
|
||||
• AI assistance: Use conversational AI and document generation to summarize transcripts and shape follow-up work.
|
||||
• Team collaboration: Share team meetings and related records with invited members and manage member roles.
|
||||
• Data controls: Export or restore account data, manage registered devices, and request account deletion.
|
||||
|
||||
Plans and ads
|
||||
The Free plan may show Google AdMob banner ads. When a user chooses to complete a rewarded ad, in-app AI usage credits are granted only after server verification. Optional Google Play subscriptions may provide additional usage and an ad-free experience. Current price, renewal, and cancellation terms are shown in the purchase screen and Google Play billing flow.
|
||||
|
||||
Privacy and responsible use
|
||||
Microphone permission is requested when recording starts. Audio and text are processed only as needed when the user invokes a cloud feature, and ad requests begin only after the applicable consent and privacy-options flow. The privacy policy and account-deletion path remain available from Settings.
|
||||
|
||||
Automated transcripts and AI-generated output can be incorrect. Review important facts and decisions yourself, and do not treat the output as professional medical, legal, financial, or safety advice. Follow applicable law and obtain any required consent before recording another person or sharing content.
|
||||
```
|
||||
|
||||
## 지원 정보
|
||||
|
||||
| 필드 | 입력안 | 상태 |
|
||||
| ---------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 지원 이메일 | `yunchan8804@gmail.com` | **후보**. 공개 법적 페이지와 일치. 배포 전 수신·응답 가능 여부 확인 |
|
||||
| 개인정보처리방침 | `https://d3ro.chanpaca.net/privacy/` | **확정**, 앱 Settings 링크와 일치 |
|
||||
| 이용약관 | `https://d3ro.chanpaca.net/terms/` | **확정**, 앱 가입·Settings·Paywall 링크와 일치 |
|
||||
| 계정 삭제 | `https://d3ro.chanpaca.net/delete-account/` | **확정**, Data safety의 삭제 URL 후보 |
|
||||
| 웹사이트 | 입력 보류 | **차단 아님**. `https://d3ro.chanpaca.net/` 루트가 지원 사이트로서 200과 유효한 콘텐츠를 제공하기 전에는 등록하지 않는다 |
|
||||
| 전화번호 | 비워 둠 | 선택 필드. 실제 공개 지원 번호가 생기기 전에는 임의 값 금지 |
|
||||
|
||||
## 등록정보 제출 전 대조
|
||||
|
||||
- 설명에 넣은 기능은 release AAB를 Play internal track으로 설치한 실제 화면에서 모두 접근 가능해야 한다.
|
||||
- `Free` reviewer 계정에서 배너와 보상형 광고 경로를, 별도 유료 reviewer 계정에서 광고 미요청과 구독 기능을 확인한다.
|
||||
- AI 신고 source와 local tests는 구현·GREEN이지만 아직 production migration/function 배포, purge scheduler·운영 담당 연결과 Play 설치본 E2E가 없다. 이 운영 경로가 GREEN이 되기 전에는 AI 보조 문구가 포함된 등록정보를 제출하지 않는다.
|
||||
- Play icon 512×512와 canonical SVG source는 `docs/v3/play/assets/`에 생성됐고 Android legacy/adaptive/monochrome launcher resource에도 반영됐다. Pixel Launcher emulator의 adaptive circle mask는 `assets/proof/d3ro-icon-pixel-launcher-20260824.png`로 확인됐다. 다만 Play-signed release 설치본과 실제 Pixel/Samsung 등 OEM mask·scale 검증은 아직 필요하다.
|
||||
- feature graphic 1024×500 후보 4개와 재현·해시 기록은 `assets/feature-graphic/`에 준비됐다. 현재 권장안은 A지만 사용자 선택과 Console preview 검증 전에는 최종 자산으로 확정하지 않는다.
|
||||
- 실제 앱 화면 스크린샷 최소 2장(권장 4장 이상)은 아직 준비해야 한다. 로그인/초대 E2E 증거 이미지는 스토어 자산으로 사용하지 않는다.
|
||||
- 자막이나 합성 UI가 실제 기능을 과장하지 않는지 확인한다. [Google의 등록정보 정확성 기준](https://support.google.com/googleplay/android-developer/answer/15191715?hl=en)을 따른다.
|
||||
|
||||
## 코드 근거
|
||||
|
||||
- 녹음·foreground service: `apps/mobile-rn/src/screens/RecordScreen.tsx`, `apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingService.kt`
|
||||
- 미디어 가져오기 MIME 경계: `apps/mobile-rn/android/app/src/main/AndroidManifest.xml:88-116`
|
||||
- 클라우드→기기 내 전사 fallback: `apps/mobile-rn/src/features/import/audio-transcription-service.ts:739-786`
|
||||
- AI 대화: `apps/mobile-rn/src/screens/TalkScreen.tsx`, `apps/mobile-rn/src/features/talk/llm-stream-service.ts`
|
||||
- 팀 협업: `apps/mobile-rn/src/screens/TeamsScreen.tsx`, `apps/mobile-rn/src/screens/TeamDetailScreen.tsx`
|
||||
- 광고 eligibility·동의: `apps/mobile-rn/src/lib/mobile-ads-context.tsx:65-175`
|
||||
- 비개인화 배너·보상형 광고: `apps/mobile-rn/src/components/FreeTierBanner.tsx:44-73`, `apps/mobile-rn/src/lib/mobile-ads-context.tsx:225-240`
|
||||
- Play 구독: `apps/mobile-rn/src/lib/billing-context.tsx`, `apps/mobile-rn/src/screens/ProPaywallScreen.tsx`
|
||||
- 데이터 이동성·삭제: `apps/mobile-rn/src/features/data-portability/`, `apps/mobile-rn/src/screens/AccountScreen.tsx:410-455`
|
||||
121
docs/v3/play/02-data-safety-draft.md
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Data safety 초안
|
||||
|
||||
## 제출 원칙
|
||||
|
||||
이 문서는 현재 Android 코드와 공개 개인정보처리방침을 바탕으로 만든 **보수적 초안**이다. Google Play의 Data safety는 특정 사용자 한 명의 기본 흐름이 아니라 Play에서 배포하는 모든 활성 버전·지역·연령의 데이터 처리를 합산해 답해야 한다. release AAB의 최종 SDK/manifest, 운영 provider 설정, 계약상 service-provider 지위와 실제 네트워크 흐름을 확인하기 전에는 제출하지 않는다.
|
||||
|
||||
상태 기준:
|
||||
|
||||
- **확정**: 코드 또는 Google SDK 공식 disclosure로 데이터 흐름이 확인됨
|
||||
- **후보**: 현재 정보로 선택할 답변안
|
||||
- **검증 필요**: 실제 release/provider 증거에 따라 답이 바뀔 수 있음
|
||||
- **보수적 fallback**: 검증을 끝내지 못했다면 더 넓게 공개할 답
|
||||
|
||||
## Console 상위 질문
|
||||
|
||||
| 질문 | 답변안 | 상태·근거 |
|
||||
| ----------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 앱이 필수 사용자 데이터 유형을 수집하거나 공유하는가? | `예` | **확정**. 계정·콘텐츠·광고·결제·기기 등록이 존재 |
|
||||
| 전송 중 데이터가 암호화되는가? | `예` | **후보**. release는 cleartext를 막음(`apps/mobile-rn/android/app/build.gradle:372-379`). 실제 release endpoint와 provider 전체가 HTTPS인지 재검증 |
|
||||
| 사용자가 데이터 삭제를 요청할 수 있는가? | `예` | **확정**. 앱 내 삭제와 외부 요청 경로 존재 |
|
||||
| 계정 생성 기능이 있는가? | `예` | **확정**. 이메일/OAuth 가입 제공 |
|
||||
| 독립적인 보안 검토를 받았는가? | `아니요` | 인증된 독립 보안 표준·배지가 실제로 생기기 전에는 선택 금지 |
|
||||
| Families 정책을 따르기로 약정하는가? | `아니요/해당 없음` | 타깃 연령 후보는 18세 이상이며 아동 대상 앱이 아님 |
|
||||
|
||||
## 데이터 유형별 입력 초안
|
||||
|
||||
`공유`는 단순히 외부 서버로 전송하는지와 다르다. Google이 인정하는 service-provider, 사용자 주도 전송 등 예외가 성립하는지는 계약과 실제 사용 목적을 증명해야 한다. 아래에서 `아니요*`는 그 예외가 입증될 때만 유효하다.
|
||||
|
||||
| Google Play 데이터 유형 | 수집 | 공유 | 목적 후보 | 선택/필수 | 일시 처리 | 판단·증거 |
|
||||
| ------------------------------------------- | ------------ | ------------ | ----------------------------------- | ----------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 대략적 위치 | 예 | 예 | 광고, 분석, 사기 방지·보안 | 필수(광고가 활성인 Free 흐름) | 아니요 | **확정**. Google Mobile Ads SDK가 IP 주소를 자동 수집·공유하고 일반 위치 추정에 사용할 수 있음 |
|
||||
| 정확한 위치 | 아니요 | 아니요 | — | — | — | 위치 permission과 기능이 없음. release merged manifest 재확인 |
|
||||
| 이름 | 예 | 아니요\* | 앱 기능, 계정 관리, 팀 협업 | 필수(가입 프로필) | 아니요 | 가입 표시 이름·프로필. Supabase service-provider 예외와 팀원에게 보이는 사용자 주도 공유 범위 검증 |
|
||||
| 이메일 주소 | 예 | 아니요\* | 앱 기능, 계정 관리, 팀 초대, 보안 | 필수(계정) | 아니요 | 이메일 로그인/OAuth와 팀 초대. 인증·메일 provider 계약 검증 |
|
||||
| 사용자 ID | 예 | 아니요\* | 앱 기능, 계정 관리, 사기 방지·보안 | 필수 | 아니요 | Supabase user UUID, 팀/결제/보상/기기 owner ID |
|
||||
| 기타 개인정보 | 예 | 아니요\* | 앱 기능, 개인화, 계정 관리 | 필수/일부 선택 | 아니요 | locale, 테마, 모델 선호, tier, 팀 역할 등. Console 질문에서 이 범주 적용 여부 최종 대조 |
|
||||
| 주소·전화번호·인종/민족·정치/종교·성적 지향 | 아니요 | 아니요 | — | — | — | 현재 모바일 기능에서 요청하지 않음. reviewer/support 폼과 provider가 추가 수집하지 않는지 확인 |
|
||||
| 사용자 결제 정보 | 아니요 | 아니요 | — | — | — | 카드 번호는 앱이 수집·저장하지 않고 Google Play 결제 UI가 처리. Android 앱에서 별도 결제 수단을 추가하면 재평가 |
|
||||
| 구매 내역 | 예 | 아니요\* | 앱 기능, 계정 관리, 사기 방지·보안 | 선택(구매 사용자) | 아니요 | 상품 ID, purchase token, 거래/구독 상태를 서버 검증·보관. Google Play와의 전송이 공유 예외인지 최종 대조 |
|
||||
| 건강·피트니스 정보 | 아니요 | 아니요 | — | — | — | 관련 기능/permission 없음 |
|
||||
| 이메일 또는 문자 메시지 | 아니요 | 아니요 | — | — | — | 사람 간 메신저가 없음. 팀 초대 주소와 내용이 이 유형에 해당하는지 Console 정의를 다시 확인 |
|
||||
| 사진 | 아니요 | 아니요 | — | — | — | 사진 선택·업로드 기능 없음 |
|
||||
| 동영상 | 예 | 아니요\* | 앱 기능 | 선택 | 아니요 | `video/mp4` 가져오기를 지원하고 전사에 사용. STT provider service-provider 지위에 따라 공유를 `예`로 바꿀 수 있음 |
|
||||
| 오디오 파일 | 예 | 아니요\* | 앱 기능 | 선택 | 아니요 | 녹음/가져오기, 클라우드 전사·동기화. 기기 내 fallback만 사용한 경우와 클라우드 경로를 모두 합산해야 함 |
|
||||
| 파일 및 문서 | 예 | 아니요\* | 앱 기능 | 선택 | 아니요 | knowledge 파일, 내보내기/복원, 생성 문서. storage·embedding provider 계약 검증 |
|
||||
| 기타 사용자 제작 콘텐츠 | 예 | 아니요\* | 앱 기능, 개인화, 사기 방지·보안 | 선택 | 아니요 | 전사문, 메모, 프롬프트, AI 대화, 템플릿, 회의·팀 데이터. AI 신고 시 사용자가 화면에서 확인하고 제출한 AI 응답 snapshot(최대 4,000자), 사유와 선택 comment(최대 500자)를 moderation 증거로 보관. pending/reviewing 증거는 해결 전 유지하고, dismissed/actioned 증거는 최종 처리 180일 후 purge 대상이다. AI provider 전송과 초대 팀원 공유에 예외가 성립하지 않으면 `공유: 예` |
|
||||
| 앱 상호작용 | 예 | 예 | 앱 기능, 광고, 분석, 사기 방지·보안 | 필수(광고 활성 Free 흐름) | 아니요 | Google Mobile Ads SDK가 앱 실행·탭·동영상 조회 등 product interaction을 자동 수집·공유. 자체 기능 사용 기록도 존재 |
|
||||
| 인앱 검색 기록 | 예 | 아니요\* | 앱 기능 | 선택 | **검증 필요** | knowledge query가 서버 검색 경로로 전송됨. 서버/provider log에 남지 않는 것이 입증돼야 `일시 처리: 예` 가능; 현재 fallback은 `아니요` |
|
||||
| 기타 사용자 활동/작업 | 예 | 아니요\* | 앱 기능, 사기 방지·보안 | 선택 | 아니요 | 사용자가 실행한 action history와 처리 상태. 계정 로컬/서버 저장 범위를 재확인 |
|
||||
| 웹 탐색 기록·설치된 앱 | 아니요 | 아니요 | — | — | — | 관련 permission·기능 없음 |
|
||||
| 비정상 종료 로그 | 아니요(후보) | 아니요(후보) | — | — | — | 전용 crash SDK는 현재 의존성에서 확인되지 않음. release SDK graph와 Google/Firebase 설정에서 자동 수집 여부 재확인 |
|
||||
| 진단 | 예 | 예 | 분석, 사기 방지·보안 | 필수(광고 활성 Free 흐름) | 아니요 | Google Mobile Ads SDK가 앱/SDK 성능 정보를 자동 수집·공유. 자체 제한 로그·오류 상태도 처리 |
|
||||
| 기기 또는 기타 ID | 예 | 예 | 앱 기능, 광고, 분석, 사기 방지·보안 | 필수(광고/푸시 활성 흐름) | 아니요 | 광고 ID·app set ID는 Google Mobile Ads SDK, FCM push token과 설치 등록 ID는 알림 기능에서 사용 |
|
||||
|
||||
## `공유: 아니요*`를 확정하기 위한 증거
|
||||
|
||||
다음 조건을 하나라도 충족하지 못하면 해당 데이터 유형은 보수적으로 `공유: 예`로 제출한다.
|
||||
|
||||
1. Supabase, STT, LLM/embedding, 알림·메일 provider가 개발자 지시에 따라서만 데이터를 처리하는 service provider인지 최신 계약/DPA로 확인한다.
|
||||
2. provider가 자기 광고, 독립 분석, 모델 학습 또는 별도 제품 개선 목적으로 데이터를 사용하지 않도록 운영 설정과 계약을 확인한다.
|
||||
3. 팀원에게 콘텐츠를 보여주는 흐름이 사용자의 명시적 초대와 선택에 의한 것인지 확인한다. 공개 피드나 자동 공개가 생기면 재분류한다.
|
||||
4. 운영 로그에 prompt, transcript, audio URL, 이메일 또는 purchase token 원문이 들어가지 않는지 검증한다.
|
||||
5. 신고 sheet가 첨부될 AI 응답을 사용자에게 미리 보여주고, 사용자가 제출한 해당 응답 snapshot·사유·선택 comment만 전송하는지 확인한다. 음성, 전체 대화와 원래 prompt는 자동 첨부하지 않는다. 30일 generation receipt, 해결 전 보관, 해결 후 180일 evidence, 계정 삭제 시 identity unlink와 purge 기준을 privacy 문서에 반영한다.
|
||||
|
||||
## SDK/provider 대조표
|
||||
|
||||
| 구성요소 | 예상 데이터 | 현재 근거 | 제출 전 확인 |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Supabase Auth/DB/Storage/Edge | 이메일, user ID, 프로필, 사용자 콘텐츠, 파일, entitlement, 기기 등록 | `apps/mobile-rn/src/lib/supabase.ts`와 각 feature service | production project의 region, retention, logs, DPA, subprocessor, 삭제 cascade |
|
||||
| Google Mobile Ads + UMP | IP 기반 대략적 위치, 앱 상호작용, 진단, 광고/계정 ID, 동의 상태 | `react-native-google-mobile-ads` 16.3.4; `mobile-ads-context.tsx` | [공식 SDK disclosure](https://developers.google.com/admob/android/privacy/play-data-disclosure), 실제 SDK 버전, UMP 메시지 게시 상태 |
|
||||
| Firebase Cloud Messaging | FCM token/설치·기기 식별 정보, allowlisted 알림 event | manifest의 messaging service, `notification-service.ts` | [Firebase disclosure](https://firebase.google.com/docs/android/play-data-disclosure), production `google-services.json`, provider retention |
|
||||
| Google Play Billing | 상품·구매 token, 거래·구독 상태, 계정 연결용 hash | `billing-context.tsx`, `iap-verify`, RTDN | release dependency graph, 상품/base plan, Play Developer API와 RTDN 운영 설정 |
|
||||
| STT provider | 선택한 음성/동영상과 언어, 전사 결과 | `audio-transcription-service.ts`, `stt-proxy` | 실제 production provider, ZDR/학습 opt-out, 로그·보관, 실패 fallback |
|
||||
| LLM/embedding provider | prompt, transcript/문서 일부, AI 결과, safety identifier | `llm-proxy`, `embed-chunks`, `search-knowledge` | production model/provider, ZDR/학습 opt-out, abuse log와 보관, 신고 반영 |
|
||||
| 자체 신고 원장 | reporter ID, generation receipt/target, reason, optional comment, reporter-selected AI output snapshot, status·review audit metadata | `20260824000029_content_reporting.sql`, `content-report` Edge Function, `ContentReportSheet.tsx` | source·service-only purge RPC와 local SQL/HTTP/Deno 검증은 GREEN. production migration/function 배포, purge scheduler·운영 담당, 개인정보처리방침과 E2E가 아직 필요 |
|
||||
|
||||
신고 데이터 contract:
|
||||
|
||||
- AI 요청은 `X-D3RO-Generation-Purpose`에 `talk_response`, `command_response`, `action_response` 중 하나를 보내고 성공 응답에서 서버 발급 `X-D3RO-Generation-Id` UUID를 받는다.
|
||||
- generation receipt는 user ID, purpose, model과 생성/만료 시각만 30일 동안 유지하며 raw prompt나 전체 AI 생성 결과를 저장하지 않는다.
|
||||
- 신고는 UUID `Idempotency-Key`, `ai_output`, 동일 source type/generation ID, reason enum, 사용자가 확인해 제출한 snapshot 1..4,000자와 optional comment 1..500자를 처리한다.
|
||||
- pending/reviewing 증거는 해결 전 보관하고, dismissed/actioned 처리 시 180일 `evidence_expires_at`을 설정한다. 계정 삭제 시 reporter/reviewer identity는 NULL로 분리한다.
|
||||
- service-only purge RPC가 만료 receipt와 해결 후 180일이 지난 report를 삭제한다. 자동 scheduler와 운영 담당은 아직 production gate다.
|
||||
- Edge Function과 일반 audit log는 prompt, 전체 generation 또는 신고 snapshot 원문을 기록하지 않고 제한된 code/status metadata만 남긴다.
|
||||
|
||||
## 개인정보처리방침 정합성 게이트
|
||||
|
||||
현재 `site/public/privacy/index.html:19-35`는 계정, 사용자 콘텐츠, 기기·알림, 결제·광고 검증, 운영 로그와 AdMob을 폭넓게 고지한다. 그러나 Console 제출 전 다음 구체화가 필요하다.
|
||||
|
||||
- AdMob 자동 데이터인 IP 기반 대략적 위치, 앱 상호작용, 진단, 광고/기기 식별자를 명시한다.
|
||||
- Google Mobile Ads가 광고·분석·사기 방지 목적으로 해당 데이터를 수집·공유할 수 있음을 명시한다.
|
||||
- AI·UGC 신고 시 수집하는 reporter/target/reason/optional comment/신고자가 선택한 AI 응답 snapshot/status, 검토 목적을 명시한다. generation receipt는 30일, 해결된 evidence는 180일 후 삭제 대상이며 계정 삭제 시 reporter/reviewer identity는 NULL로 분리된다는 기준도 명시한다.
|
||||
- production STT/AI/embedding provider의 실제 명칭 또는 범주, 국제 이전·보관·학습 사용 여부를 정확히 맞춘다.
|
||||
- 공개 방침의 수정일을 올리고 live URL의 본문·제목·HTTPS 200을 재검증한다.
|
||||
|
||||
## 최종 검증 절차
|
||||
|
||||
1. production secrets로 `bundleRelease`를 실행해 `app-release.aab`를 만든다. debug/E2E 산출물을 사용하지 않는다.
|
||||
2. release merged manifest에서 `AD_ID`, Privacy Sandbox AdServices, Billing, FCM, microphone/notification/FGS permission을 기록한다.
|
||||
3. Gradle release dependency report와 Play SDK Index 경고를 보존하고 SDK 버전을 위 표와 맞춘다.
|
||||
4. Play internal track에 AAB를 업로드하고 **Play가 서명·생성한 설치본**으로 아래 흐름을 각각 확인한다.
|
||||
- 새 사용자/기존 사용자
|
||||
- Free + 동의 허용/거부/개인정보 옵션
|
||||
- Pro/Pro+ entitlement + 광고 요청 없음
|
||||
- 구매/복원/환불·취소 RTDN
|
||||
- 마이크 녹음, 파일/MP4 가져오기, cloud 성공·실패 후 local fallback
|
||||
- AI 대화/문서 생성과 신고
|
||||
- 팀 공유와 탈퇴/삭제
|
||||
- 계정 삭제 후 서버·로컬 데이터 경계
|
||||
5. Android Network Inspector, provider dashboard와 서버의 redacted audit log를 대조해 표에 없는 endpoint/데이터가 없는지 확인한다.
|
||||
6. provider 계약·운영 설정 증거와 release SHA/version/package/certificate를 함께 보관한다.
|
||||
7. `purge_expired_content_reporting_data_v1()`를 호출하는 인증된 자동 scheduler와 운영 담당을 연결하고, 만료 receipt와 해결 180일 경과 report가 실제 삭제되는 production 증거를 남긴다.
|
||||
8. Console preview를 개인정보처리방침과 한 줄씩 대조하고, 불명확한 항목은 보수적으로 공개한다.
|
||||
|
||||
## Google 공식 기준
|
||||
|
||||
- [Data safety form 안내와 데이터 유형](https://support.google.com/googleplay/android-developer/answer/10787469?hl=en)
|
||||
- [Google Mobile Ads SDK data disclosure](https://developers.google.com/admob/android/privacy/play-data-disclosure)
|
||||
- [Firebase Android data disclosure](https://firebase.google.com/docs/android/play-data-disclosure)
|
||||
- [계정 삭제 요구사항](https://support.google.com/googleplay/android-developer/answer/13327111?hl=en)
|
||||
- [Advertising ID 정책](https://support.google.com/googleplay/android-developer/answer/6048248?hl=en)
|
||||
255
docs/v3/play/03-app-content-draft.md
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
# App content 답변 초안
|
||||
|
||||
## 1. 광고
|
||||
|
||||
| 질문 | 입력안 | 근거 |
|
||||
| ----------------------- | ------ | ------------------------------------------------------------------------------------ |
|
||||
| 앱에 광고가 포함되는가? | **예** | Free 사용자를 위한 AdMob adaptive banner와 사용자가 직접 실행하는 rewarded ad가 있음 |
|
||||
|
||||
콘솔 메모:
|
||||
|
||||
```text
|
||||
D3RO Voice uses Google AdMob banner ads for eligible Free-plan users and offers an optional rewarded ad that grants non-transferable in-app AI usage credits only after server-side verification. Paid entitlements do not request ads.
|
||||
```
|
||||
|
||||
코드 근거:
|
||||
|
||||
- 광고 eligibility는 로그인 사용자이며 최신 entitlement가 `free`이고 ad-free가 아닐 때만 성립: `apps/mobile-rn/src/lib/mobile-ads-context.tsx:65-80`
|
||||
- UMP 동의 결과가 광고 요청을 허용한 뒤 SDK 초기화: `apps/mobile-rn/src/lib/mobile-ads-context.tsx:89-137`
|
||||
- banner는 비개인화 요청: `apps/mobile-rn/src/components/FreeTierBanner.tsx:44-73`
|
||||
- rewarded ad는 사용자가 실행하고 SSV user ID/custom data를 설정: `apps/mobile-rn/src/lib/mobile-ads-context.tsx:216-240`
|
||||
|
||||
## 2. Advertising ID
|
||||
|
||||
| 질문 | 입력안 | 상태 |
|
||||
| ------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 앱 또는 포함된 SDK가 Advertising ID를 사용하는가? | **예** | **후보/사실상 필수**. Google Mobile Ads SDK가 release manifest에 `com.google.android.gms.permission.AD_ID`를 병합할 수 있음 |
|
||||
| 사용 목적 | 광고 또는 마케팅, 분석, 사기 방지·보안 | Google Mobile Ads 공식 disclosure와 맞춤 |
|
||||
|
||||
제출 직전 release merged manifest에서 아래를 다시 캡처한다.
|
||||
|
||||
```text
|
||||
com.google.android.gms.permission.AD_ID
|
||||
android.permission.ACCESS_ADSERVICES_AD_ID
|
||||
android.permission.ACCESS_ADSERVICES_ATTRIBUTION
|
||||
android.permission.ACCESS_ADSERVICES_TOPICS
|
||||
```
|
||||
|
||||
소스 manifest에 직접 보이지 않더라도 SDK library manifest에서 병합될 수 있다. E2E merged manifest에는 위 항목이 확인됐지만, Console 선언은 **최종 production AAB** 기준으로 확정한다.
|
||||
|
||||
## 3. 앱 액세스
|
||||
|
||||
### Console 선택
|
||||
|
||||
`앱 기능의 전체 또는 일부가 로그인, 멤버십, 위치 또는 다른 형태의 인증으로 제한됨`을 선택한다. D3RO Voice의 핵심 기능은 Supabase 계정 로그인이 필요하다.
|
||||
|
||||
실제 reviewer credential은 저장소나 release note에 넣지 않고 Play Console의 App access 필드에만 저장한다. 아래 placeholder를 실계정으로 교체한다.
|
||||
|
||||
### Credential set A — Free/ads path
|
||||
|
||||
```text
|
||||
Name: D3RO Voice reviewer — Free plan
|
||||
Username/email: <PLACEHOLDER_PLAY_REVIEWER_FREE_EMAIL>
|
||||
Password: <PLACEHOLDER_PLAY_REVIEWER_FREE_PASSWORD>
|
||||
|
||||
Instructions (English):
|
||||
1. Launch D3RO Voice. On a fresh install, complete the onboarding screens and choose the existing-account sign-in path.
|
||||
2. Choose email/password sign-in. Do not use Google OAuth.
|
||||
3. Enter the reusable credentials above. No OTP, 2-step verification, location restriction, or expiring password is required.
|
||||
4. This account is on the Free plan with sufficient quota. It can access recording, import, transcription, Talk, meetings, templates, knowledge, teams, banner ads, rewarded ads, privacy options, data export, and account settings.
|
||||
5. To review rewarded ads, open the plan/paywall screen and use the rewarded-ad control. Credits appear only after server verification.
|
||||
6. To review AI reporting, open Talk, generate an AI response, and tap Report next to that assistant response. The sheet previews the selected response, lets the reviewer choose a reason and optional comment, and returns a report receipt after submission.
|
||||
```
|
||||
|
||||
### Credential set B — Pro+/ad-free and team path
|
||||
|
||||
```text
|
||||
Name: D3RO Voice reviewer — Pro+ and team
|
||||
Username/email: <PLACEHOLDER_PLAY_REVIEWER_PRO_PLUS_EMAIL>
|
||||
Password: <PLACEHOLDER_PLAY_REVIEWER_PRO_PLUS_PASSWORD>
|
||||
|
||||
Instructions (English):
|
||||
1. Follow the same onboarding and email/password sign-in steps as credential set A.
|
||||
2. This reusable account has an active reviewer-only Pro+ entitlement and belongs to a seeded private review team. It does not require the reviewer to make a purchase.
|
||||
3. Open Works > Teams > <PLACEHOLDER_REVIEW_TEAM_NAME> to review invited-team collaboration, roles, meetings, memos, and shared records.
|
||||
4. The Pro+ entitlement is ad-free; no ad request should be made for this account.
|
||||
5. Use Settings > Account for account management and the in-app deletion path. Do not delete the shared reviewer account; the separate deletion procedure is documented below.
|
||||
```
|
||||
|
||||
### 계정 운영 게이트
|
||||
|
||||
- 두 계정 모두 이메일 확인 완료, 비밀번호 만료 없음, OTP/2FA/CAPTCHA 없음, 전 세계 reviewer 네트워크에서 24시간 재사용 가능해야 한다.
|
||||
- seed 데이터에는 실사용자·실회의·민감정보를 넣지 않는다.
|
||||
- Free 계정에는 광고와 AI 신고를 검토할 충분한 quota를 둔다.
|
||||
- Pro+ entitlement는 reviewer 계정에만 운영적으로 부여하고 만료 모니터를 둔다.
|
||||
- 매 release 제출 직전 깨끗한 Play 설치본으로 두 계정을 실제 로그인한다.
|
||||
|
||||
[Google의 로그인 정보 요구사항](https://support.google.com/googleplay/android-developer/answer/15748846?hl=en)을 따른다.
|
||||
|
||||
## 4. 콘텐츠 등급(IARC)과 타깃 연령
|
||||
|
||||
### 타깃 연령 후보
|
||||
|
||||
- 선택: **18세 이상만**
|
||||
- 아동 대상 여부: **아니요**
|
||||
- 아동에게 의도적으로 호소하는 그래픽·문구·캐릭터: **아니요**
|
||||
|
||||
이 선택은 앱이 성인물이라는 뜻이 아니라, 녹음·업로드·생성형 AI·초대형 팀 협업과 광고/구독을 포함하는 업무 생산성 도구를 아동 대상으로 운영하지 않겠다는 제품 경계다. 실제 target-audience 질문과 국가별 의무를 Console에서 다시 확인한다.
|
||||
|
||||
### IARC 답변 지침
|
||||
|
||||
IARC 결과 등급을 미리 단정하지 않는다. 질문에 아래 사실을 정확히 반영하고 Console이 산출한 지역별 등급을 수용한다.
|
||||
|
||||
| 항목 | 사실 기반 답변안 |
|
||||
| --------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| 앱 유형 | 생산성/유틸리티, 게임 아님 |
|
||||
| 광고 | 예 |
|
||||
| 디지털 구매 | 예, 자동 갱신 Google Play 구독 가능 |
|
||||
| 생성형 AI | 예, 음성/텍스트 입력에 대한 대화 및 문서 결과 생성 |
|
||||
| 사용자 제작 콘텐츠 | 예, 사용자가 녹음·입력·업로드한 전사, 메모, 문서, 팀 기록 |
|
||||
| 사용자 간 콘텐츠 교환/온라인 상호작용 | 예(보수적), 초대된 비공개 팀 안에서 회의와 관련 기록 공유 |
|
||||
| 공개 피드·불특정 사용자 채팅 | 아니요 |
|
||||
| 사용자 차단/신고 | release에서 구현·운영 검증 후 실제 질문에 답변. 미구현 상태에서는 제출 차단 |
|
||||
| 개발자가 제공하는 폭력·성적·도박·약물 콘텐츠 | 아니요 |
|
||||
| 실제 현금 도박·상금·베팅 | 아니요 |
|
||||
| 사용자가 입력하거나 녹음할 수 있는 민감/불쾌 콘텐츠 | 가능. 신고·moderation 정책으로 처리하며 이를 숨기지 않음 |
|
||||
|
||||
광고는 앱의 최종 IARC 등급보다 현저히 성숙한 콘텐츠를 보여주면 안 된다. 현재 광고 SDK request configuration은 최대 광고 등급 `T`를 사용한다(`mobile-ads-context.tsx:95-98`). 최종 IARC 결과가 더 낮으면 광고 등급을 그 이하로 내리고 재검증한다.
|
||||
|
||||
## 5. 금융 기능 선언
|
||||
|
||||
후보 선택:
|
||||
|
||||
```text
|
||||
My app doesn't provide any financial features.
|
||||
```
|
||||
|
||||
근거:
|
||||
|
||||
- Google Play 구독은 앱의 디지털 기능 이용 권한을 구매하는 일반 in-app purchase다.
|
||||
- rewarded ad의 `cloud_ai_tokens`는 D3RO Voice 안에서만 소비되는 AI 사용량 quota다.
|
||||
- 이 quota는 현금 가치, 인출, 송금, 교환, 사용자 간 이전, 투자, 암호자산 또는 외부 상품 교환 기능이 없다.
|
||||
|
||||
따라서 현재 제품은 금융 서비스나 `Rewards, points, frequent flier miles, and other incentives`형 금융 프로그램으로 운영하지 않는다는 후보 판단이다. **크레딧을 양도·판매·환전·외부 보상에 사용하게 바꾸면 즉시 이 선언을 재검토한다.** 최종 저장 전 [Google의 금융 기능 선언 분류](https://support.google.com/googleplay/android-developer/answer/13849271?hl=en)와 Console의 최신 항목을 그대로 대조한다.
|
||||
|
||||
## 6. 계정 삭제
|
||||
|
||||
| 질문 | 입력안 | 근거 |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------ |
|
||||
| 앱에서 계정을 만들 수 있는가? | 예 | 이메일/OAuth 가입 |
|
||||
| 앱 안에서 계정 삭제를 요청할 수 있는가? | 예 | Settings > Account > Delete account |
|
||||
| 앱 밖에서 삭제를 요청할 수 있는가? | 예 | 아래 공개 URL |
|
||||
| 삭제 URL | `https://d3ro.chanpaca.net/delete-account/` | 앱 내·외 절차, 삭제/제한 보관 범주 기재 |
|
||||
| 일부 데이터만 삭제하는 별도 경로 | 계정 전체 삭제와 데이터 내보내기 제공. 별도 부분 삭제 질문은 실제 기능별로 확인 | 데이터 이동성 및 각 기록 삭제 UI 대조 필요 |
|
||||
|
||||
삭제 설명 후보:
|
||||
|
||||
```text
|
||||
Users can delete their account in D3RO Voice from Settings > Account > Delete account. The authenticated server deletes the account and associated service data, after which the app purges account-scoped local caches, queued audio, recordings, action history, preferences, entitlement caches, and push registration. Users who cannot access the app can request deletion at https://d3ro.chanpaca.net/delete-account/. Minimum payment, dispute, security, and abuse-prevention records may be retained only where required for those purposes.
|
||||
```
|
||||
|
||||
`20260824000029_content_reporting.sql`은 계정 삭제 시 report의 `reporter_id`와 review actor identity를 NULL로 분리하고 안전 신고 증거는 유지한다. pending/reviewing 증거는 최종 처리 전에는 time purge하지 않으며, dismissed/actioned 증거에는 처리 시점부터 180일의 `evidence_expires_at`을 설정한다. service-only purge RPC도 구현됐다. 다만 자동 scheduler·운영 담당 연결과 이 기준의 privacy/deletion 페이지 반영 전에는 위 삭제 답변을 최종 제출하지 않는다.
|
||||
|
||||
근거: `apps/mobile-rn/src/screens/AccountScreen.tsx:69-94,410-455`, `apps/mobile-rn/src/lib/account-local-data.ts:21-49`, `site/public/delete-account/index.html:18-39`.
|
||||
|
||||
## 7. AI 생성 콘텐츠와 UGC
|
||||
|
||||
### 현재 분류
|
||||
|
||||
- Talk의 text-to-text AI 응답과 전사 기반 문서 생성이 있으므로 Google Play의 AI-Generated Content 정책 적용 대상이다.
|
||||
- 초대된 팀 안에서 회의·전사·메모·문서를 공유하므로 제한적 UGC/online interaction도 있다.
|
||||
- 공개 피드나 익명 대화방은 없다. 그렇더라도 AI 결과 신고 요구사항은 면제되지 않는다.
|
||||
|
||||
### release 차단 조건
|
||||
|
||||
현재 source에는 Talk assistant response의 `Report` 버튼, 신고 sheet, authenticated Edge Function, generation receipt, service-only moderation 원장·purge RPC와 manager/admin review RPC가 구현돼 있다. 클라이언트는 `X-D3RO-Generation-Purpose`에 `talk_response`, `command_response`, `action_response` 중 하나를 보내고, 성공 응답의 서버 발급 `X-D3RO-Generation-Id`를 신고 source로 사용한다. 신고 POST는 UUID `Idempotency-Key`, `ai_output`, 동일 source type/generation ID, reason, 선택 snapshot/comment를 사용한다. generation receipt는 user·purpose·model·timestamps만 저장하고 raw prompt나 전체 생성 결과를 저장하지 않으며, Edge/general audit log도 snapshot 원문을 기록하지 않는다. 근거는 `apps/mobile-rn/src/screens/TalkScreen.tsx`, `apps/mobile-rn/src/components/ContentReportSheet.tsx`, `apps/mobile-rn/src/features/reporting/content-report-service.ts`, `server/supabase/functions/_shared/generation-receipt.ts`, `server/supabase/functions/content-report/`, `server/supabase/migrations/20260824000029_content_reporting.sql`이다. **source 구현과 local GREEN은 production 배포·운영 moderation·Play 설치본 E2E 완료를 의미하지 않는다.**
|
||||
|
||||
아래가 모두 GREEN이 되기 전에는 AI 기능을 포함한 release를 어떤 심사 트랙에도 제출하지 않는다.
|
||||
|
||||
- [x] generation receipt 30일 유효, source 3종, request/response header와 idempotent 신고 contract가 source에 구현됐다.
|
||||
- [x] local SQL integration, HTTP Edge 15 assertions와 shared Deno 64 tests가 GREEN이다.
|
||||
- [ ] 모든 AI 생성 결과 화면에서 앱을 벗어나지 않고 offensive content를 신고할 수 있다.
|
||||
- [ ] Play release 설치본에서 신고자가 인증돼 있고, 신고 sheet가 선택된 AI 응답을 미리 보여주는지 확인한다. 사용자가 제출하면 generation ID·reason·선택 comment와 해당 AI 응답 snapshot만 전송하며, 음성·전체 대화·원래 prompt는 자동 첨부하지 않는다.
|
||||
- [ ] production 원장에서 RLS, 허용 target/reason, 길이 제한, rate limit, 중복/남용 방어가 실제로 적용되는지 재검증한다.
|
||||
- [ ] 운영자용 검토 상태, 감사 기록, 처리 절차와 책임자가 있다.
|
||||
- [ ] 이용약관/사용자 정책이 금지 콘텐츠·행동, 신고·조치 가능성을 구체적으로 설명한다.
|
||||
- [ ] 개인정보처리방침이 reporter/target/reason/comment/AI 응답 snapshot, 목적, 공유, 30일 receipt, 해결 전 보관, 해결 후 180일 evidence, identity unlink와 purge를 설명한다.
|
||||
- [ ] service-only purge RPC에 자동 scheduler를 연결하고 운영 담당·실패 알림·runbook을 지정했다.
|
||||
- [ ] production migration/API/UI 배포 후 reviewer 계정으로 신고→운영 검토 원장 도달 E2E 증거를 남긴다.
|
||||
- [ ] `<PLACEHOLDER_REPORT_TEST_EVIDENCE>`를 실제 증거 링크/경로로 교체한다.
|
||||
|
||||
정책 근거: [AI-Generated Content](https://support.google.com/googleplay/android-developer/answer/13985936?hl=en), [Developer Program Policy](https://support.google.com/googleplay/android-developer/answer/17190352?hl=en&rd=2).
|
||||
|
||||
## 8. Foreground service 선언
|
||||
|
||||
### 유형과 사용 사례
|
||||
|
||||
| 필드 | 입력안 |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| FGS type | `microphone` |
|
||||
| Console use case | `Background audio access` / `Voice recording` |
|
||||
| permission | `android.permission.FOREGROUND_SERVICE`, `android.permission.FOREGROUND_SERVICE_MICROPHONE`, `android.permission.RECORD_AUDIO` |
|
||||
| service | `.D3RORecordingService`, exported `false` |
|
||||
|
||||
manifest 근거: `apps/mobile-rn/android/app/src/main/AndroidManifest.xml:4-9,49-53`.
|
||||
|
||||
### 기능 설명 — Console 입력안
|
||||
|
||||
```text
|
||||
D3RO Voice starts a microphone foreground service only after the signed-in user taps Record. The service captures the user-requested recording while the user may navigate to another app screen or place the app in the background. An ongoing notification clearly states that the D3RO Voice microphone is active, shows recording progress, and provides Pause/Resume, Stop, and Discard controls. The user can terminate the task at any time, and the foreground service and notification stop when the recording is stopped or discarded.
|
||||
```
|
||||
|
||||
### 지연·중단 영향 — Console 입력안
|
||||
|
||||
```text
|
||||
If start is deferred, the beginning of the user-requested conversation can be missed. If the task is interrupted, the recording can contain an unrecoverable gap and the resulting transcript can be incomplete. The app journals and repairs a partial WAV when possible, but it cannot reconstruct audio that the system did not capture. Immediate, user-visible foreground execution is therefore required for the core recording feature.
|
||||
```
|
||||
|
||||
코드 근거:
|
||||
|
||||
- 사용자 녹음 시작과 permission 요청: `apps/mobile-rn/src/screens/RecordScreen.tsx:320-347`
|
||||
- Android foreground service 시작: `apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingModule.kt:98-111`
|
||||
- ongoing notification과 Pause/Resume, Stop, Discard: `apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingService.kt:528-590`
|
||||
- 최대 wake lock과 종료 처리: `D3RORecordingService.kt:449-465,588-628`
|
||||
|
||||
### 영상 URL
|
||||
|
||||
```text
|
||||
<PLACEHOLDER_FGS_DEMO_URL>
|
||||
```
|
||||
|
||||
영상은 실제 release 후보를 설치한 Android 14+ 실기기에서 다음 순서를 한 번에 보여준다.
|
||||
|
||||
1. 앱 정보 화면 또는 화면 오버레이로 package, versionName/versionCode와 Play 설치본임을 식별한다.
|
||||
2. D3RO Voice 로그인 후 Record 화면을 연다.
|
||||
3. 녹음 시작을 탭하고, 처음 실행이면 앱 내 설명 뒤 microphone/notification runtime prompt를 보여준다.
|
||||
4. 녹음이 시작되자마자 ongoing notification에 microphone active와 timer가 표시되는 것을 보여준다.
|
||||
5. 홈으로 나가거나 화면을 잠갔다가 notification shade를 열어 녹음이 계속되고 있음을 보여준다.
|
||||
6. notification의 Pause와 Resume을 각각 사용한다.
|
||||
7. Stop으로 종료하고 notification이 사라진 뒤 앱에서 녹음/전사 처리 상태를 보여준다.
|
||||
8. 별도 짧은 녹음을 시작해 Discard로 즉시 종료할 수 있음을 보여준다.
|
||||
|
||||
영상에는 실제 대화나 개인정보를 녹음하지 않는다. 비공개/로그인 제한 없이 reviewer가 열 수 있는 unlisted YouTube 또는 공개 읽기 전용 Drive URL을 사용하고 제출 직전 링크를 익명 창에서 확인한다.
|
||||
|
||||
[Google FGS 선언 안내](https://support.google.com/googleplay/android-developer/answer/13392821?hl=en)를 따른다.
|
||||
|
||||
## 9. 기타 App content 후보
|
||||
|
||||
| 항목 | 입력안 |
|
||||
| -------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| 개인정보처리방침 | `https://d3ro.chanpaca.net/privacy/` |
|
||||
| 뉴스/매거진 앱 | 아니요 |
|
||||
| 정부 앱 | 아니요 |
|
||||
| 건강 앱/의료 기능 | 아니요 |
|
||||
| COVID-19 기능 | 아니요 |
|
||||
| 데이터 브로커/대출/도박/암호화폐 | 아니요 |
|
||||
| Families/교사 승인 | 신청하지 않음 |
|
||||
| 민감 권한 | microphone, notification, microphone FGS. 위치·연락처·통화 기록·SMS 권한 없음 |
|
||||
|
||||
## 최종 저장 전 확인
|
||||
|
||||
- [ ] 실제 Console 질문 문구와 이 문서의 후보 답을 한 항목씩 대조했다.
|
||||
- [ ] release AAB가 선언한 permission/SDK와 답이 일치한다.
|
||||
- [ ] Data safety, 개인정보처리방침, 광고·AI·UGC·FGS 답변 사이에 모순이 없다.
|
||||
- [ ] 모든 `<PLACEHOLDER_...>`를 실제 값으로 교체했다.
|
||||
- [ ] 사용자 본인이 정책·수출법·Play App Signing 약관과 각 App content 선언을 검토하고 제출을 승인했다.
|
||||
343
docs/v3/play/04-release-checklist.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
# 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 후보 4개와 SVG 원본·PNG 해시·재현 절차를 준비했다.
|
||||
- [ ] 사용자 선택 및 Console preview 검증으로 feature graphic 최종본을 확정했다.
|
||||
- [ ] 실제 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] 기본 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`.
|
||||
- [ ] Console upload 후 alpha/모서리/안전 영역 경고가 없는지 확인했다.
|
||||
- [x] feature graphic 1024×500 후보 4개와 재현·해시 기록을 준비했다: `docs/v3/play/assets/feature-graphic/`.
|
||||
- [ ] 후보 중 최종본을 사용자 선택 후 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 |
|
||||
184
docs/v3/play/05-content-reporting-operations.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# AI 콘텐츠 신고 운영 runbook
|
||||
|
||||
기준일: 2026-08-24
|
||||
|
||||
이 문서는 D3RO Voice의 AI 결과 신고를 production에서 접수·검토·종결·정리하는
|
||||
절차다. 현재는 구현과 local 검증만 완료됐으며, production 배포·운영 담당 지정·자동
|
||||
purge·실제 설치본 E2E는 완료되지 않았다.
|
||||
|
||||
## 1. 책임과 접근 경계
|
||||
|
||||
출시 전에 다음 값을 실제 담당자로 교체한다.
|
||||
|
||||
| 역할 | 책임 | 현재 상태 |
|
||||
| ----------------- | -------------------------------------------------- | -------------------------------- |
|
||||
| moderation owner | 매 영업일 queue 확인, SLA와 종결 품질 관리 | `<PLACEHOLDER_MODERATION_OWNER>` |
|
||||
| safety escalation | 아동 안전, 자해·폭력, 명백한 불법 콘텐츠 즉시 판단 | `<PLACEHOLDER_SAFETY_ON_CALL>` |
|
||||
| privacy owner | 개인정보·계정 삭제·보관 예외 검토 | `<PLACEHOLDER_PRIVACY_OWNER>` |
|
||||
| purge owner | 매일 purge 실행·실패 알림·월간 보관 감사 | `<PLACEHOLDER_PURGE_OWNER>` |
|
||||
|
||||
- 일반 사용자는 authenticated `content-report` Edge Function으로만 신고한다.
|
||||
- `content_generation_receipts`, `content_reports`,
|
||||
`content_report_review_actions`는 `anon`과 `authenticated`의 직접 접근을 모두
|
||||
거부한다.
|
||||
- queue 조회·상태 변경 RPC는 `profiles.role`이 `manager`, `admin`,
|
||||
`super_admin`인 인증 사용자만 호출할 수 있다.
|
||||
- `profiles.role`과 `profiles.tier`는 일반 사용자의 column UPDATE 권한에서 제외한다.
|
||||
표시 이름, 아바타, locale만 self-service 수정이 가능하다.
|
||||
- service-role key는 모바일 앱, 브라우저, 로그, 티켓, Play reviewer 안내에 넣지 않는다.
|
||||
|
||||
근거 마이그레이션:
|
||||
|
||||
- `server/supabase/migrations/20260824000029_content_reporting.sql`
|
||||
- `server/supabase/migrations/20260824000030_profile_managed_field_acl.sql`
|
||||
|
||||
## 2. Production 배포 순서
|
||||
|
||||
다음 순서를 한 release window 안에서 수행한다.
|
||||
|
||||
1. 권위 있는 release commit과 production Supabase project ref를 기록한다.
|
||||
2. migration `20260824000029`, `20260824000030`의 dry-run diff를 검토한 뒤 적용한다.
|
||||
3. `llm-proxy`와 `content-report`를 같은 source SHA에서 배포한다.
|
||||
4. Talk, Commands, Actions 클라이언트를 포함한 AAB를 배포한다.
|
||||
5. purge job과 실패 알림을 활성화한다.
|
||||
6. Free/Pro+ 테스트 계정으로 제출→queue→검토→종결→감사 원장 E2E를 수행한다.
|
||||
|
||||
예시 명령의 placeholder는 실행 전에 secret manager와 release record의 실제 값으로
|
||||
대체한다. 이 문서의 명령 실행 자체가 production 배포 승인인 것은 아니다.
|
||||
|
||||
```powershell
|
||||
supabase db push --linked --dry-run
|
||||
supabase db push --linked
|
||||
supabase functions deploy llm-proxy --project-ref '<PROJECT_REF>'
|
||||
supabase functions deploy content-report --project-ref '<PROJECT_REF>'
|
||||
```
|
||||
|
||||
배포 직후 다음을 확인한다.
|
||||
|
||||
- `X-D3RO-Generation-Purpose`가
|
||||
`talk_response|command_response|action_response`만 허용된다.
|
||||
- 성공한 provider 응답에는 UUID `X-D3RO-Generation-Id`가 있다.
|
||||
- receipt에는 user/model/purpose/expiry만 있고 prompt·AI 출력은 없다.
|
||||
- 신고에는 사용자가 미리 확인한 snapshot, reason, 선택 comment만 있다.
|
||||
- 음성, 전체 대화, 원래 prompt는 자동 첨부되지 않는다.
|
||||
- 일반 사용자 role/tier 직접 UPDATE와 ledger 직접 SELECT/INSERT가 거부된다.
|
||||
|
||||
## 3. Queue와 처리 SLA
|
||||
|
||||
운영 도구는 로그인한 manager 이상 사용자의 access token으로 다음 RPC를 호출한다.
|
||||
브라우저 콘솔이나 공유 스크립트에 token을 붙여 넣지 않는다.
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.rpc('admin_list_content_reports_v1', {
|
||||
p_status: 'pending',
|
||||
p_limit: 100,
|
||||
p_before: null
|
||||
})
|
||||
```
|
||||
|
||||
권장 SLA:
|
||||
|
||||
- 아동 성적 학대·착취 또는 즉각적인 생명 위험: 발견 즉시 escalation
|
||||
- 개인정보 노출, 구체적 폭력 위협, 자해 조장: 4시간 이내 1차 검토
|
||||
- 혐오·괴롭힘·성적·사기·스팸·오정보·기타: 1영업일 이내 1차 검토
|
||||
- 미처리 `pending`의 최장 age가 24시간을 넘으면 moderation owner와 on-call에 알림
|
||||
|
||||
검토자는 snapshot과 사용자가 선택한 사유·comment만 필요한 범위에서 본다. 다른
|
||||
대화, 녹음, 계정 데이터는 자동으로 확장 조회하지 않는다.
|
||||
|
||||
## 4. 검토와 종결
|
||||
|
||||
각 조치는 새 UUID idempotency key와 3~1000자의 사실 기반 note가 필요하다.
|
||||
|
||||
```ts
|
||||
await supabase.rpc('admin_act_on_content_report_v1', {
|
||||
p_report_id: reportId,
|
||||
p_idempotency_key: crypto.randomUUID(),
|
||||
p_action: 'begin_review',
|
||||
p_note: 'Initial policy review started'
|
||||
})
|
||||
```
|
||||
|
||||
허용 전이는 다음뿐이다.
|
||||
|
||||
| action | 결과 상태 | 사용 조건 |
|
||||
| ------------------- | ----------- | -------------------------------------------- |
|
||||
| `begin_review` | `reviewing` | 담당자가 증거를 확인하기 시작함 |
|
||||
| `dismiss` | `dismissed` | 정책 위반이 아니거나 증거가 불충분함 |
|
||||
| `confirm_violation` | `actioned` | 정책 위반을 확인하고 별도 완화 조치를 기록함 |
|
||||
|
||||
`confirm_violation`은 원장 상태를 확정하지만 계정 정지나 provider 차단을 자동 실행하지
|
||||
않는다. 필요한 계정·모델·prompt 정책 조치는 기존 승인된 관리자 절차에서 별도로
|
||||
수행하고, 신고 note에는 비밀이나 snapshot 원문을 복제하지 말고 조치 식별자만 남긴다.
|
||||
|
||||
의도적인 허위·보복·반복 남용 신고는 신고 자체를 삭제해 숨기지 않는다. `dismiss`로
|
||||
종결하고 반복 패턴은 별도 abuse case로 escalation한다. 선의의 신고자에게 불이익을
|
||||
주지 않는다.
|
||||
|
||||
## 5. 보관과 purge
|
||||
|
||||
- generation receipt: 생성 후 30일
|
||||
- `pending`, `reviewing` 신고 증거: 해결할 때까지
|
||||
- `dismissed`, `actioned` 신고 증거: 해결 시점부터 180일
|
||||
- 계정 삭제: reporter/reviewer identity와 moderation audit actor는 `NULL`로 분리하지만,
|
||||
미해결 또는 180일 보관 창의 선택 증거는 moderation·분쟁 대응을 위해 남을 수 있다.
|
||||
- general audit log에는 snapshot이나 전체 생성 결과를 복제하지 않는다.
|
||||
|
||||
정리는 `purge_expired_content_reporting_data_v1()`만 사용한다. 이 RPC는
|
||||
service-role만 실행할 수 있다. scheduler는 secret manager에서 service credential을
|
||||
주입하는 서버 측 job이어야 하며, 모바일·브라우저 job은 금지한다.
|
||||
|
||||
성공 응답 예시는 다음과 같다.
|
||||
|
||||
```json
|
||||
{ "purgedReports": 3, "purgedReceipts": 121 }
|
||||
```
|
||||
|
||||
매일 실행 결과에 실행 시각, release SHA, 숫자만 기록한다. report ID, snapshot,
|
||||
comment, service credential은 scheduler log에 남기지 않는다. 24시간 동안 성공 실행이
|
||||
없거나 RPC가 실패하면 purge owner에게 알리고, 72시간 이내 복구되지 않으면 privacy
|
||||
owner에게 escalation한다.
|
||||
|
||||
## 6. 모니터링과 월간 감사
|
||||
|
||||
운영 모니터는 원문 없이 다음 집계만 보관한다.
|
||||
|
||||
- status별 수와 최장 pending age
|
||||
- 시간·일 rate-limit 발생 수
|
||||
- 제출 4xx/5xx와 idempotency conflict 수
|
||||
- review action 성공·실패 수
|
||||
- 마지막 purge 성공 시각과 삭제 건수
|
||||
|
||||
매월 다음을 표본 확인한다.
|
||||
|
||||
- 일반 사용자 role/tier self-update가 계속 거부되는가
|
||||
- expired receipt가 남아 있지 않은가
|
||||
- resolved report가 180일 이후 purge되는가
|
||||
- pending/reviewing 증거가 time purge되지 않는가
|
||||
- audit log에 snapshot·comment·prompt가 복제되지 않았는가
|
||||
- manager/admin/super_admin 이외 계정이 moderation RPC를 호출하지 못하는가
|
||||
|
||||
## 7. Release 증거와 rollback
|
||||
|
||||
release evidence에는 비밀이나 신고 원문 없이 다음만 보관한다.
|
||||
|
||||
- source commit SHA와 migration/function deployment ID
|
||||
- local SQL, Edge HTTP, Deno test 결과
|
||||
- Play 설치본의 Talk/Commands/Actions 신고 왕복 캡처
|
||||
- queue 도달, `begin_review`, terminal action의 report ID 해시와 상태 시각
|
||||
- purge job 설정, 마지막 성공 시각, alert test
|
||||
- privacy/terms 공개 URL과 해당 버전의 hash
|
||||
|
||||
배포 후 신고 제출이나 generation receipt가 실패하면 AI 생성 경로를 정상처럼 계속
|
||||
노출하지 않는다. 영향 surface를 비활성화하거나 이전 검증 release로 rollback하고,
|
||||
receipt와 report schema를 임의로 drop하거나 moderation evidence를 수동 삭제하지 않는다.
|
||||
|
||||
## 현재 미해결 gate
|
||||
|
||||
- production migration/Edge/mobile 동시 배포
|
||||
- manager용 moderation UI 또는 승인된 내부 operator client
|
||||
- 실제 운영 담당자와 on-call 지정
|
||||
- 매일 purge scheduler와 실패 알림
|
||||
- provider safety/filter 검증
|
||||
- Play가 서명한 실기기 신고 E2E
|
||||
- Talk/Commands/Actions 밖의 AI 생성 문서 신고 범위
|
||||
62
docs/v3/play/README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# D3RO Voice Google Play 제출 패키지
|
||||
|
||||
- 기준일: 2026-08-25
|
||||
- 대상 패키지: `com.d3ro.voice`
|
||||
- 기본 언어: `ko-KR`
|
||||
- 보조 언어: `en-US`
|
||||
|
||||
이 디렉터리는 Google Play Console에 입력할 후보 문구와 제출 전 검증 게이트를 모은다. **초안은 콘솔에서 자동으로 제출해도 된다는 승인이 아니다.** 실제 release AAB, Play가 서명한 설치본, 운영 provider 설정과 콘솔 질문을 다시 대조한 뒤 사람이 최종 선언해야 한다.
|
||||
|
||||
## 문서
|
||||
|
||||
1. [`01-store-listing.md`](01-store-listing.md): 한국어·영어 스토어 등록정보와 지원 정보
|
||||
2. [`02-data-safety-draft.md`](02-data-safety-draft.md): Data safety 데이터 유형별 초안과 검증 항목
|
||||
3. [`03-app-content-draft.md`](03-app-content-draft.md): 광고, 앱 액세스, 등급, 금융 기능, 계정 삭제, AI·UGC, FGS 답변
|
||||
4. [`04-release-checklist.md`](04-release-checklist.md): 앱 생성부터 internal/closed/production까지의 출시 게이트
|
||||
5. [`05-content-reporting-operations.md`](05-content-reporting-operations.md): AI 신고 검토·종결·보관·purge 운영 절차
|
||||
6. [`assets/feature-graphic/README.md`](assets/feature-graphic/README.md): 1024×500 후보, 원본, 재현 절차와 해시
|
||||
7. [`../e2e/RED_E2E_500_CONTENT_REPORT.md`](../e2e/RED_E2E_500_CONTENT_REPORT.md): local Gateway→Edge→DB 650개 AI 신고 Red-path E2E
|
||||
|
||||
## 상태 표기
|
||||
|
||||
- **확정**: 현재 코드·정적 설정 또는 공개 운영 URL로 근거가 확인됐다.
|
||||
- **후보**: 현재 구현과 맞는 콘솔 입력안이지만 실제 질문 문구와 다시 대조해야 한다.
|
||||
- **검증 필요**: release AAB, provider 계약·설정, 실제 계정 또는 실기기 증거가 필요하다.
|
||||
- **차단**: 해결 전에는 해당 선언이나 트랙 제출을 진행하면 안 된다.
|
||||
- `<PLACEHOLDER_...>`: 실제 계정·인증서·URL·영상 값으로 교체해야 한다. 그대로 제출하면 안 된다.
|
||||
|
||||
## 현재 사실 경계
|
||||
|
||||
- Play Console 앱은 `YUN CHAN` 개인 개발자 계정에서 생성됐다: app ID `4976102237698469110`, `D3RO Voice`, `com.d3ro.voice`, `ko-KR`, 앱·무료. 정책·수출법 선언은 사용자 action-time 확인 후 처리됐다. 앱 생성은 제출·internal track·production 승인과 다르다.
|
||||
- Android 소스는 API 36을 target/compile하고 microphone foreground service, Google Mobile Ads, Firebase Cloud Messaging, Google Play Billing을 포함한다.
|
||||
- 공개 개인정보처리방침, 이용약관, 계정 삭제 페이지는 아래 canonical URL을 사용한다. 제출 직전 익명 브라우저에서 다시 확인한다.
|
||||
- `https://d3ro.chanpaca.net/privacy/`
|
||||
- `https://d3ro.chanpaca.net/terms/`
|
||||
- `https://d3ro.chanpaca.net/delete-account/`
|
||||
- 현재 작업공간에는 **Play 제출용 production AAB가 없다**. debug/E2E APK 또는 과거 self-hosted APK는 Play release 증거가 아니다.
|
||||
- D3RO legacy/adaptive/monochrome launcher resource와 512×512 Play icon source는 반영됐다. JDK 17 양 ABI debug build, 기존 데이터 보존 update install과 Pixel Launcher emulator의 adaptive circle mask는 GREEN이다. proof는 `docs/v3/play/assets/proof/d3ro-icon-pixel-launcher-20260824.png`(SHA-256 `7A2DA876FBFD54089B783A7FD45C2472476ED47DCEABA08AB305B7B01A18D430`)다. Play-signed release와 실제 Pixel/Samsung/OEM mask·scale 증거는 아직 남았다.
|
||||
- Play App Signing의 app-signing certificate와 개발자가 보관하는 upload certificate는 서로 다른 신뢰 경계다. Firebase OAuth/App Links에는 Play app-signing fingerprint를 등록하고, AAB 업로드 검증에는 upload fingerprint를 사용한다.
|
||||
- AI 신고 source는 generation receipt, authenticated `content-report` Edge Function, service-only moderation 원장과 purge RPC까지 구현됐고 local SQL/HTTP/Deno 검증이 GREEN이다. receipt는 30일 유효하며 해결된 신고 증거는 180일 후 purge 대상이다. 운영 runbook 초안도 준비됐지만 production 배포, 자동 purge scheduler, 실제 담당 지정과 Play 설치본 E2E는 아직 남았다.
|
||||
- local disposable Supabase에서 AI 신고 Red-path E2E 650개가 GREEN이다. malformed body·권한 우회·source ownership·idempotency·rate limit·manager moderation 전이를 실제 Gateway→Edge→DB로 검증하며 production endpoint는 실행기에서 fail-closed한다.
|
||||
|
||||
## 주요 코드 근거
|
||||
|
||||
- 패키지·release 설정: `apps/mobile-rn/android/app/build.gradle`
|
||||
- target API: `apps/mobile-rn/android/build.gradle`
|
||||
- 권한·FGS·AdMob·App Links: `apps/mobile-rn/android/app/src/main/AndroidManifest.xml`
|
||||
- 광고와 UMP: `apps/mobile-rn/src/lib/mobile-ads-context.tsx`, `apps/mobile-rn/src/components/FreeTierBanner.tsx`
|
||||
- Play Billing: `apps/mobile-rn/src/lib/billing-context.tsx`, `apps/mobile-rn/src/screens/ProPaywallScreen.tsx`
|
||||
- 계정 삭제: `apps/mobile-rn/src/screens/AccountScreen.tsx`, `apps/mobile-rn/src/lib/account-local-data.ts`
|
||||
- AI 신고: `apps/mobile-rn/src/components/ContentReportSheet.tsx`, `apps/mobile-rn/src/features/reporting/content-report-service.ts`, `server/supabase/functions/content-report/`, `server/supabase/migrations/20260824000029_content_reporting.sql`, `server/supabase/migrations/20260824000030_profile_managed_field_acl.sql`, `server/supabase/migrations/20260825000031_audit_log_actor_unlink.sql`
|
||||
- 법적 링크: `apps/mobile-rn/src/screens/SignUpScreen.tsx`, `apps/mobile-rn/src/screens/SettingsScreen.tsx`
|
||||
- 공개 법적 문서: `site/public/privacy/index.html`, `site/public/terms/index.html`, `site/public/delete-account/index.html`
|
||||
|
||||
## Google 공식 기준
|
||||
|
||||
- [Create and set up your app](https://support.google.com/googleplay/android-developer/answer/9859152?hl=en)
|
||||
- [Provide information for the Data safety section](https://support.google.com/googleplay/android-developer/answer/10787469?hl=en)
|
||||
- [Prepare your app for review](https://support.google.com/googleplay/android-developer/answer/9859455?hl=en)
|
||||
- [Use Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en)
|
||||
- [Target API level requirements](https://support.google.com/googleplay/android-developer/answer/11926878?hl=en)
|
||||
- [App testing requirements for new personal developer accounts](https://support.google.com/googleplay/android-developer/answer/14151465?hl=en)
|
||||
- [Support 16 KB page sizes](https://developer.android.com/guide/practices/page-sizes)
|
||||
8
docs/v3/play/assets/d3ro-voice-legacy-icon-source.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<rect width="512" height="512" rx="112" fill="#19191b"/>
|
||||
<circle cx="256" cy="224" r="64" fill="none" stroke="#3b82f6" stroke-width="24"/>
|
||||
<rect x="232" y="288" width="48" height="80" rx="24" fill="#3b82f6"/>
|
||||
<path d="M160 256 Q160 352 256 352 Q352 352 352 256" fill="none" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
<line x1="256" y1="352" x2="256" y2="416" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
<line x1="208" y1="416" x2="304" y2="416" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
BIN
docs/v3/play/assets/d3ro-voice-play-icon-512.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
8
docs/v3/play/assets/d3ro-voice-play-icon-source.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<rect width="512" height="512" fill="#19191b"/>
|
||||
<circle cx="256" cy="224" r="64" fill="none" stroke="#3b82f6" stroke-width="24"/>
|
||||
<rect x="232" y="288" width="48" height="80" rx="24" fill="#3b82f6"/>
|
||||
<path d="M160 256 Q160 352 256 352 Q352 352 352 256" fill="none" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
<line x1="256" y1="352" x2="256" y2="416" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
<line x1="208" y1="416" x2="304" y2="416" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 611 B |
8
docs/v3/play/assets/d3ro-voice-round-icon-source.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<circle cx="256" cy="256" r="256" fill="#19191b"/>
|
||||
<circle cx="256" cy="224" r="64" fill="none" stroke="#3b82f6" stroke-width="24"/>
|
||||
<rect x="232" y="288" width="48" height="80" rx="24" fill="#3b82f6"/>
|
||||
<path d="M160 256 Q160 352 256 352 Q352 352 352 256" fill="none" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
<line x1="256" y1="352" x2="256" y2="416" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
<line x1="208" y1="416" x2="304" y2="416" stroke="#3b82f6" stroke-width="20" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 614 B |
57
docs/v3/play/assets/feature-graphic/README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Google Play feature graphic 후보
|
||||
|
||||
기준일: 2026-08-24
|
||||
|
||||
Google Play의 1024×500 feature graphic 규격에 맞춘 선택 후보다. 네 후보 모두
|
||||
저장소의 canonical D3RO microphone motif와 `D3RO VOICE` 표기만 사용하며, 기능이나
|
||||
성과를 주장하는 문구와 합성 앱 화면은 포함하지 않는다.
|
||||
|
||||
## 후보와 권장안
|
||||
|
||||
| 후보 | 방향 | PNG SHA-256 |
|
||||
| ------------------------------ | --------------------------------------- | ------------------------------------------------------------------ |
|
||||
| A `candidate-a-signal-grid` | dark signal grid, microphone + waveform | `3CFA86A4843339FD9C15781598E301ECE29C2B1522828F7AD1B69D8497775103` |
|
||||
| B `candidate-b-voice-orbit` | centered voice orbit | `F7FCB26B063762D08AC1211A85CFDDD3C99C14936CD6DE753F5E9F88BC0B0D14` |
|
||||
| C `candidate-c-blueprint-band` | light/dark blueprint split | `C484AC514966347BF4E7AA526B040B88DDA89F9150AC3677B8C74297A1373C1E` |
|
||||
| D `candidate-d-monolith` | bold dark monolith | `28FC7B608454E073A29AFE2149414F4186629AE8889B3FE2FA683D786F8C3EA9` |
|
||||
|
||||
현재 권장안은 **A**다. 작은 스토어 카드에서도 microphone, 제품명, 파형이 가장
|
||||
명확하게 분리된다. 다만 최종 자산 확정과 Play Console 업로드는 사용자 선택 및
|
||||
Console preview 검증 뒤에만 진행한다.
|
||||
|
||||
## 원본과 재현
|
||||
|
||||
- brand source: `site/public/favicon.svg`
|
||||
- Play icon source: `docs/v3/play/assets/d3ro-voice-play-icon-source.svg`
|
||||
- 편집 원본: 각 후보의 `.svg`
|
||||
- 제출 후보: 같은 basename의 `.png`
|
||||
- export runtime: ImageMagick `7.1.2-25 Q16-HDRI`
|
||||
- 출력 조건: 1024×500, PNG, sRGB, 8-bit, TrueColor, 완전 불투명
|
||||
- machine-readable manifest: `manifest.json`
|
||||
- verifier: `scripts/ci/verify-play-store-assets.mjs`
|
||||
|
||||
저장소 루트의 PowerShell에서 다음 명령으로 다시 내보낸다.
|
||||
|
||||
```powershell
|
||||
Get-ChildItem docs/v3/play/assets/feature-graphic/candidate-*.svg |
|
||||
ForEach-Object {
|
||||
$output = [System.IO.Path]::ChangeExtension($_.FullName, '.png')
|
||||
magick $_.FullName -background none -resize '1024x500!' -alpha off -colorspace sRGB "PNG24:$output"
|
||||
}
|
||||
```
|
||||
|
||||
환경이나 SVG renderer 버전이 바뀌면 raster hash가 달라질 수 있다. 제출할 PNG는
|
||||
위 해시, 크기, 불투명 여부를 다시 확인하고 그 파일을 release evidence에 기록한다.
|
||||
|
||||
```powershell
|
||||
npm run release:play:assets
|
||||
```
|
||||
|
||||
## SVG 원본 해시
|
||||
|
||||
| 파일 | SHA-256 |
|
||||
| -------------------------------- | ------------------------------------------------------------------ |
|
||||
| `candidate-a-signal-grid.svg` | `3A39D0D61F5AE7E182149BD11C9D7F491FBEC0A542999B0516C869AE82E24B1B` |
|
||||
| `candidate-b-voice-orbit.svg` | `DB7D778394419C6AF8FF43C7490B43C0CD65C61CE012A358FB37E603431082F4` |
|
||||
| `candidate-c-blueprint-band.svg` | `176BEB99EA418F78BF5F9DC4E4D6A3A3700F5CEADA28697F3750B0C8883E4546` |
|
||||
| `candidate-d-monolith.svg` | `6560C334C061470959D9BA120E58CCA264B5A1ED41FBC3045F804049117640FF` |
|
||||
BIN
docs/v3/play/assets/feature-graphic/candidate-a-signal-grid.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -0,0 +1,17 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 500">
|
||||
<rect width="1024" height="500" fill="#19191b"/>
|
||||
<g stroke="#2b2b31" stroke-width="1">
|
||||
<path d="M0 50H1024M0 100H1024M0 150H1024M0 200H1024M0 250H1024M0 300H1024M0 350H1024M0 400H1024M0 450H1024"/>
|
||||
<path d="M64 0V500M128 0V500M192 0V500M256 0V500M320 0V500M384 0V500M448 0V500M512 0V500M576 0V500M640 0V500M704 0V500M768 0V500M832 0V500M896 0V500M960 0V500"/>
|
||||
</g>
|
||||
<rect x="64" y="54" width="290" height="392" rx="42" fill="#111114" stroke="#3b82f6" stroke-width="2"/>
|
||||
<g transform="translate(98 62) scale(4.35)" fill="none" stroke="#3b82f6" stroke-linecap="round">
|
||||
<circle cx="32" cy="28" r="8" stroke-width="3"/>
|
||||
<rect x="29" y="36" width="6" height="10" rx="3" fill="#3b82f6" stroke="none"/>
|
||||
<path d="M20 32Q20 44 32 44Q44 44 44 32" stroke-width="2.5"/>
|
||||
<path d="M32 44V52M26 52H38" stroke-width="2.5"/>
|
||||
</g>
|
||||
<text x="430" y="227" fill="#f5f7fb" font-family="Arial, sans-serif" font-size="78" font-weight="700" letter-spacing="7">D3RO</text>
|
||||
<text x="434" y="288" fill="#3b82f6" font-family="Arial, sans-serif" font-size="40" font-weight="700" letter-spacing="15">VOICE</text>
|
||||
<path d="M432 352H484L500 326L526 390L548 340L568 362H620L640 312L664 396L690 346L710 366H774L792 333L812 378L834 352H944" fill="none" stroke="#3b82f6" stroke-width="5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
docs/v3/play/assets/feature-graphic/candidate-b-voice-orbit.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 500">
|
||||
<rect width="1024" height="500" fill="#0f1014"/>
|
||||
<circle cx="512" cy="250" r="205" fill="#19191b" stroke="#292c35" stroke-width="2"/>
|
||||
<circle cx="512" cy="250" r="166" fill="none" stroke="#3b82f6" stroke-opacity="0.22" stroke-width="28"/>
|
||||
<circle cx="512" cy="250" r="126" fill="none" stroke="#3b82f6" stroke-opacity="0.45" stroke-width="3" stroke-dasharray="5 17"/>
|
||||
<g transform="translate(416 106) scale(3)" fill="none" stroke="#3b82f6" stroke-linecap="round">
|
||||
<circle cx="32" cy="28" r="8" stroke-width="3"/>
|
||||
<rect x="29" y="36" width="6" height="10" rx="3" fill="#3b82f6" stroke="none"/>
|
||||
<path d="M20 32Q20 44 32 44Q44 44 44 32" stroke-width="2.5"/>
|
||||
<path d="M32 44V52M26 52H38" stroke-width="2.5"/>
|
||||
</g>
|
||||
<path d="M40 250H250M774 250H984" stroke="#3b82f6" stroke-width="2"/>
|
||||
<path d="M62 250L92 250L110 220L134 286L158 238L180 260L205 210L228 276L250 250M774 250L802 250L826 208L848 290L872 235L896 270L920 224L944 278L962 250" fill="none" stroke="#3b82f6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="401" y="386" width="222" height="46" rx="23" fill="#3b82f6"/>
|
||||
<text x="512" y="417" text-anchor="middle" fill="#0f1014" font-family="Arial, sans-serif" font-size="22" font-weight="700" letter-spacing="4">D3RO VOICE</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -0,0 +1,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 500">
|
||||
<rect width="1024" height="500" fill="#f4f6fa"/>
|
||||
<rect x="0" y="0" width="356" height="500" fill="#19191b"/>
|
||||
<rect x="356" y="0" width="28" height="500" fill="#3b82f6"/>
|
||||
<g transform="translate(42 98) scale(4.25)" fill="none" stroke="#3b82f6" stroke-linecap="round">
|
||||
<circle cx="32" cy="28" r="8" stroke-width="3"/>
|
||||
<rect x="29" y="36" width="6" height="10" rx="3" fill="#3b82f6" stroke="none"/>
|
||||
<path d="M20 32Q20 44 32 44Q44 44 44 32" stroke-width="2.5"/>
|
||||
<path d="M32 44V52M26 52H38" stroke-width="2.5"/>
|
||||
</g>
|
||||
<text x="438" y="224" fill="#19191b" font-family="Arial, sans-serif" font-size="82" font-weight="700" letter-spacing="5">D3RO</text>
|
||||
<text x="443" y="287" fill="#3b82f6" font-family="Arial, sans-serif" font-size="38" font-weight="700" letter-spacing="17">VOICE</text>
|
||||
<g stroke="#cbd2df" stroke-width="2">
|
||||
<path d="M444 344H954"/>
|
||||
<path d="M444 372H954"/>
|
||||
<path d="M444 400H954"/>
|
||||
</g>
|
||||
<g fill="#3b82f6">
|
||||
<rect x="444" y="366" width="8" height="13" rx="4"/>
|
||||
<rect x="460" y="349" width="8" height="47" rx="4"/>
|
||||
<rect x="476" y="333" width="8" height="79" rx="4"/>
|
||||
<rect x="492" y="357" width="8" height="31" rx="4"/>
|
||||
<rect x="508" y="341" width="8" height="63" rx="4"/>
|
||||
<rect x="524" y="362" width="8" height="21" rx="4"/>
|
||||
<rect x="540" y="352" width="8" height="41" rx="4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
docs/v3/play/assets/feature-graphic/candidate-d-monolith.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
15
docs/v3/play/assets/feature-graphic/candidate-d-monolith.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 500">
|
||||
<rect width="1024" height="500" fill="#19191b"/>
|
||||
<rect x="52" y="42" width="920" height="416" rx="34" fill="#15161a" stroke="#31343d" stroke-width="2"/>
|
||||
<text x="90" y="314" fill="#f7f8fb" font-family="Arial, sans-serif" font-size="210" font-weight="700" letter-spacing="-8">D3RO</text>
|
||||
<rect x="90" y="354" width="378" height="58" rx="29" fill="#3b82f6"/>
|
||||
<text x="279" y="394" text-anchor="middle" fill="#101115" font-family="Arial, sans-serif" font-size="28" font-weight="700" letter-spacing="11">VOICE</text>
|
||||
<circle cx="826" cy="250" r="112" fill="#0e0f12" stroke="#3b82f6" stroke-width="2"/>
|
||||
<g transform="translate(730 106) scale(3)" fill="none" stroke="#3b82f6" stroke-linecap="round">
|
||||
<circle cx="32" cy="28" r="8" stroke-width="3"/>
|
||||
<rect x="29" y="36" width="6" height="10" rx="3" fill="#3b82f6" stroke="none"/>
|
||||
<path d="M20 32Q20 44 32 44Q44 44 44 32" stroke-width="2.5"/>
|
||||
<path d="M32 44V52M26 52H38" stroke-width="2.5"/>
|
||||
</g>
|
||||
<path d="M720 404H932" stroke="#3b82f6" stroke-width="3" stroke-dasharray="3 12" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
57
docs/v3/play/assets/feature-graphic/manifest.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"generatedOn": "2026-08-24",
|
||||
"selection": {
|
||||
"recommended": "A",
|
||||
"status": "pending_user_selection_and_console_preview"
|
||||
},
|
||||
"brandSource": "site/public/favicon.svg",
|
||||
"export": {
|
||||
"tool": "ImageMagick 7.1.2-25 Q16-HDRI",
|
||||
"format": "PNG24",
|
||||
"colorSpace": "sRGB",
|
||||
"width": 1024,
|
||||
"height": 500,
|
||||
"bitDepth": 8,
|
||||
"pngColorType": 2,
|
||||
"opaque": true
|
||||
},
|
||||
"candidates": [
|
||||
{
|
||||
"id": "A",
|
||||
"source": "candidate-a-signal-grid.svg",
|
||||
"sourceBytes": 1411,
|
||||
"sourceSha256": "3A39D0D61F5AE7E182149BD11C9D7F491FBEC0A542999B0516C869AE82E24B1B",
|
||||
"output": "candidate-a-signal-grid.png",
|
||||
"outputBytes": 25200,
|
||||
"outputSha256": "3CFA86A4843339FD9C15781598E301ECE29C2B1522828F7AD1B69D8497775103"
|
||||
},
|
||||
{
|
||||
"id": "B",
|
||||
"source": "candidate-b-voice-orbit.svg",
|
||||
"sourceBytes": 1365,
|
||||
"sourceSha256": "DB7D778394419C6AF8FF43C7490B43C0CD65C61CE012A358FB37E603431082F4",
|
||||
"output": "candidate-b-voice-orbit.png",
|
||||
"outputBytes": 44972,
|
||||
"outputSha256": "F7FCB26B063762D08AC1211A85CFDDD3C99C14936CD6DE753F5E9F88BC0B0D14"
|
||||
},
|
||||
{
|
||||
"id": "C",
|
||||
"source": "candidate-c-blueprint-band.svg",
|
||||
"sourceBytes": 1443,
|
||||
"sourceSha256": "176BEB99EA418F78BF5F9DC4E4D6A3A3700F5CEADA28697F3750B0C8883E4546",
|
||||
"output": "candidate-c-blueprint-band.png",
|
||||
"outputBytes": 14337,
|
||||
"outputSha256": "C484AC514966347BF4E7AA526B040B88DDA89F9150AC3677B8C74297A1373C1E"
|
||||
},
|
||||
{
|
||||
"id": "D",
|
||||
"source": "candidate-d-monolith.svg",
|
||||
"sourceBytes": 1151,
|
||||
"sourceSha256": "6560C334C061470959D9BA120E58CCA264B5A1ED41FBC3045F804049117640FF",
|
||||
"output": "candidate-d-monolith.png",
|
||||
"outputBytes": 28737,
|
||||
"outputSha256": "28FC7B608454E073A29AFE2149414F4186629AE8889B3FE2FA683D786F8C3EA9"
|
||||
}
|
||||
]
|
||||
}
|
||||
270
docs/v3/play/assets/mobile-icon-manifest.json
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"canonicalSource": {
|
||||
"path": "docs/v3/play/assets/d3ro-voice-play-icon-source.svg",
|
||||
"viewBox": "0 0 512 512",
|
||||
"sha256": "E2390BF8AA82EA357D61D8834F9FC58179227A87E26672699C63866A533B7E5B"
|
||||
},
|
||||
"playStoreIcon": {
|
||||
"path": "docs/v3/play/assets/d3ro-voice-play-icon-512.png",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"bitDepth": 8,
|
||||
"colorType": 6,
|
||||
"alpha": "opaque",
|
||||
"sha256": "0FAE53FB40CCE4B9FB0213CBEDB4E087CA00A8F8C438D6731C5C47C4AFD096B1"
|
||||
},
|
||||
"android": {
|
||||
"applicationManifest": "apps/mobile-rn/android/app/src/main/AndroidManifest.xml",
|
||||
"icon": "@mipmap/ic_launcher",
|
||||
"roundIcon": "@mipmap/ic_launcher_round",
|
||||
"background": {
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/values/colors.xml",
|
||||
"resource": "d3ro_launcher_background",
|
||||
"value": "#19191B"
|
||||
},
|
||||
"sourceAssets": [
|
||||
{
|
||||
"path": "docs/v3/play/assets/d3ro-voice-legacy-icon-source.svg",
|
||||
"viewBox": "0 0 512 512",
|
||||
"sha256": "8DDC2ACB6C8BC379611597A47FECA65DDC61237DE30B78C16F355D00FC264783"
|
||||
},
|
||||
{
|
||||
"path": "docs/v3/play/assets/d3ro-voice-round-icon-source.svg",
|
||||
"viewBox": "0 0 512 512",
|
||||
"sha256": "978BEB618DDC8BC691090AC779CDB8177C62E70BEF538C407D0FE599D7126874"
|
||||
}
|
||||
],
|
||||
"vectors": {
|
||||
"foreground": {
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/drawable/d3ro_launcher_foreground.xml",
|
||||
"sha256": "F2123246C93C1D11ED2DE530F17203C4A8F258DF244BC24146CDE0E09737345D"
|
||||
},
|
||||
"monochrome": {
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/drawable/d3ro_launcher_monochrome.xml",
|
||||
"sha256": "035F9599E46E4AB34F30EAA73A586DFAB0C086C7A653122EE3C33307E908A650"
|
||||
}
|
||||
},
|
||||
"adaptiveIcons": [
|
||||
{
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
|
||||
"monochrome": false
|
||||
},
|
||||
{
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
|
||||
"monochrome": false
|
||||
},
|
||||
{
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml",
|
||||
"monochrome": true
|
||||
},
|
||||
{
|
||||
"path": "apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml",
|
||||
"monochrome": true
|
||||
}
|
||||
],
|
||||
"legacyIcons": [
|
||||
{
|
||||
"density": "mdpi",
|
||||
"pixels": 48,
|
||||
"iconPath": "apps/mobile-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher.png",
|
||||
"iconSha256": "82C702747E3BBF2A03826989D093CA99C3CBB8A125B81F2F0631FF66A2F536EA",
|
||||
"roundPath": "apps/mobile-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png",
|
||||
"roundSha256": "202002E1C9A68035C2A2C7F1313EB73E1F0CF0578D0228F9FBCEDDA809B5D891"
|
||||
},
|
||||
{
|
||||
"density": "hdpi",
|
||||
"pixels": 72,
|
||||
"iconPath": "apps/mobile-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher.png",
|
||||
"iconSha256": "AB5FE36F55A2E58FCB5DDEC58493D21B8A10EFC9324E1D272FCFEFD25DDB7901",
|
||||
"roundPath": "apps/mobile-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png",
|
||||
"roundSha256": "7D73AEA182A82DAD88595FAB97A95C142E708993FAD806BBDE48E7A2B2B6F088"
|
||||
},
|
||||
{
|
||||
"density": "xhdpi",
|
||||
"pixels": 96,
|
||||
"iconPath": "apps/mobile-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png",
|
||||
"iconSha256": "E563FA0698BB5013837C4AFF4152BF180F48997410A57986FDD02CE4F52D5818",
|
||||
"roundPath": "apps/mobile-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png",
|
||||
"roundSha256": "1FD9924F6A201FD182A1B67FBAC8C81683A6D2450CABD02EF2EEED3D9B8B5F80"
|
||||
},
|
||||
{
|
||||
"density": "xxhdpi",
|
||||
"pixels": 144,
|
||||
"iconPath": "apps/mobile-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png",
|
||||
"iconSha256": "5347ABF45AA423A63E60638D28310F15929BAF768E0584CA414B1B23B9B3F8F5",
|
||||
"roundPath": "apps/mobile-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png",
|
||||
"roundSha256": "A9996374E5ED94F05DD0501BF02259B40C8EEA2F0373A85809921BD75EA1704F"
|
||||
},
|
||||
{
|
||||
"density": "xxxhdpi",
|
||||
"pixels": 192,
|
||||
"iconPath": "apps/mobile-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png",
|
||||
"iconSha256": "A84BC25E362397527AD048EF931816021AD3C1482982FFA2E4F1F8DF52FFFB33",
|
||||
"roundPath": "apps/mobile-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png",
|
||||
"roundSha256": "144564057C9962CC50989B3A4FCA1691B964D8EE69FF47EA37FD65F474A568A1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ios": {
|
||||
"source": "docs/v3/play/assets/d3ro-voice-play-icon-source.svg",
|
||||
"contents": "apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/Contents.json",
|
||||
"project": "apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj",
|
||||
"targetedDeviceFamilies": [1, 2],
|
||||
"bitDepth": 8,
|
||||
"colorType": 2,
|
||||
"alphaChannel": false,
|
||||
"icons": [
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-iphone-20@2x.png",
|
||||
"pixels": 40,
|
||||
"sha256": "750DB19CBEC82017CA68D622FA4DF86F9A7E11EEE49390CEBEEC283C0AB10496"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "20x20",
|
||||
"scale": "3x",
|
||||
"filename": "AppIcon-iphone-20@3x.png",
|
||||
"pixels": 60,
|
||||
"sha256": "4715F000171E6CA1CD4F31C1BB5215F91FD86F414BFB288E42F0708474E2652B"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-iphone-29@2x.png",
|
||||
"pixels": 58,
|
||||
"sha256": "1A13D610E1EEB1B508D496A017B611F36307EFBC50681613DADD634D415BFC75"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "3x",
|
||||
"filename": "AppIcon-iphone-29@3x.png",
|
||||
"pixels": 87,
|
||||
"sha256": "F8A6A88020CEC4446A9B295C8696EF2D32838F9E4074C3FCF3E0A283F1A7246E"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-iphone-40@2x.png",
|
||||
"pixels": 80,
|
||||
"sha256": "DA0796E96389EEE5D36CC390EA5850FF2DF3FC5D1B291D57BB43053FBEAC9F49"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "3x",
|
||||
"filename": "AppIcon-iphone-40@3x.png",
|
||||
"pixels": 120,
|
||||
"sha256": "56FB0B823D72C4133E4BF1E24BBB8FA9E24E8C3721A8EF608BBF1A1097052CA7"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-iphone-60@2x.png",
|
||||
"pixels": 120,
|
||||
"sha256": "56FB0B823D72C4133E4BF1E24BBB8FA9E24E8C3721A8EF608BBF1A1097052CA7"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "3x",
|
||||
"filename": "AppIcon-iphone-60@3x.png",
|
||||
"pixels": 180,
|
||||
"sha256": "1A2843E454496520359D740450C699636435D641717BA963D751D817BE79F8F6"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "20x20",
|
||||
"scale": "1x",
|
||||
"filename": "AppIcon-ipad-20@1x.png",
|
||||
"pixels": 20,
|
||||
"sha256": "399D5A67D13569E04725DA466AAE45419595D51828C286A4424DB204C8202D7F"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-ipad-20@2x.png",
|
||||
"pixels": 40,
|
||||
"sha256": "750DB19CBEC82017CA68D622FA4DF86F9A7E11EEE49390CEBEEC283C0AB10496"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "1x",
|
||||
"filename": "AppIcon-ipad-29@1x.png",
|
||||
"pixels": 29,
|
||||
"sha256": "BF07A8DB710655D650F102ABA31A74BD68BA26C10A4DB1E22D6743908CDCA61A"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-ipad-29@2x.png",
|
||||
"pixels": 58,
|
||||
"sha256": "1A13D610E1EEB1B508D496A017B611F36307EFBC50681613DADD634D415BFC75"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "1x",
|
||||
"filename": "AppIcon-ipad-40@1x.png",
|
||||
"pixels": 40,
|
||||
"sha256": "750DB19CBEC82017CA68D622FA4DF86F9A7E11EEE49390CEBEEC283C0AB10496"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-ipad-40@2x.png",
|
||||
"pixels": 80,
|
||||
"sha256": "DA0796E96389EEE5D36CC390EA5850FF2DF3FC5D1B291D57BB43053FBEAC9F49"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "1x",
|
||||
"filename": "AppIcon-ipad-76@1x.png",
|
||||
"pixels": 76,
|
||||
"sha256": "460B8F8AF51D947CEA6A40A39CA10DC4E7D774846DC4AE20275402B9B25439D6"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-ipad-76@2x.png",
|
||||
"pixels": 152,
|
||||
"sha256": "47E8DA09603F931E25E92AF77405C7C55B4CD56EA3E35DCAE3A423CE82F625B1"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "83.5x83.5",
|
||||
"scale": "2x",
|
||||
"filename": "AppIcon-ipad-83.5@2x.png",
|
||||
"pixels": 167,
|
||||
"sha256": "8EC9EEE448DAAD188AA6F730BCC80D14D1BD7707207A7573CF04E232F7E9C0D7"
|
||||
},
|
||||
{
|
||||
"idiom": "ios-marketing",
|
||||
"size": "1024x1024",
|
||||
"scale": "1x",
|
||||
"filename": "AppIcon-marketing-1024@1x.png",
|
||||
"pixels": 1024,
|
||||
"sha256": "E0D0357BAB6EB61628AF8FA50892BFA80D5F0293419A19E1E94A652B0037B1B0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pixelLauncherProof": {
|
||||
"path": "docs/v3/play/assets/proof/d3ro-icon-pixel-launcher-20260824.png",
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"sha256": "7A2DA876FBFD54089B783A7FD45C2472476ED47DCEABA08AB305B7B01A18D430"
|
||||
}
|
||||
}
|
||||
BIN
docs/v3/play/assets/proof/d3ro-icon-pixel-launcher-20260824.png
Normal file
|
After Width: | Height: | Size: 290 KiB |