ci: allow protected mobile candidate
This commit is contained in:
parent
4872d6f6c5
commit
1f71cf6fae
3 changed files with 797 additions and 9 deletions
|
|
@ -284,10 +284,11 @@ package-macos:
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/'
|
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/'
|
||||||
|
|
||||||
# Protected, manually approved Play Console handoff. Configure the three
|
# Protected, manually approved Play Console candidate/stable handoff. The main
|
||||||
# *_FILE variables below as protected GitLab file variables scoped to the
|
# branch path only creates restricted artifacts; publish-release remains tag-only.
|
||||||
# mobile-production-release environment. All other credentials must be
|
# Configure the three *_FILE variables below as protected GitLab file variables
|
||||||
# protected, masked, and hidden project/group variables.
|
# scoped to the mobile-production-release environment. All other credentials
|
||||||
|
# must be protected, masked, and hidden project/group variables.
|
||||||
mobile-production-release:
|
mobile-production-release:
|
||||||
stage: package
|
stage: package
|
||||||
image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36
|
image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36
|
||||||
|
|
@ -320,10 +321,11 @@ mobile-production-release:
|
||||||
tar -xJf /tmp/node.tar.xz -C /tmp/node24 --strip-components=1
|
tar -xJf /tmp/node.tar.xz -C /tmp/node24 --strip-components=1
|
||||||
export PATH="/tmp/node24/bin:$PATH"
|
export PATH="/tmp/node24/bin:$PATH"
|
||||||
node --version | grep -Fx "v${NODE_VERSION}"
|
node --version | grep -Fx "v${NODE_VERSION}"
|
||||||
git fetch --no-tags origin main
|
git fetch --no-tags origin main:refs/remotes/origin/main
|
||||||
git merge-base --is-ancestor "$CI_COMMIT_SHA" origin/main
|
git merge-base --is-ancestor "$CI_COMMIT_SHA" origin/main
|
||||||
test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
|
test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)"
|
||||||
test -z "$(git status --porcelain --untracked-files=all)"
|
test -z "$(git status --porcelain --untracked-files=all)"
|
||||||
|
test "${CI_COMMIT_REF_PROTECTED:-}" = "true"
|
||||||
SOURCE_TREE_SHA="$(git rev-parse "${CI_COMMIT_SHA}^{tree}")"
|
SOURCE_TREE_SHA="$(git rev-parse "${CI_COMMIT_SHA}^{tree}")"
|
||||||
VERSION_NAME="$(node -p "require('./release/product-version.json').version")"
|
VERSION_NAME="$(node -p "require('./release/product-version.json').version")"
|
||||||
VERSION_CODE="$(node -p "require('./release/product-version.json').androidVersionCode")"
|
VERSION_CODE="$(node -p "require('./release/product-version.json').androidVersionCode")"
|
||||||
|
|
@ -331,7 +333,16 @@ mobile-production-release:
|
||||||
ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")"
|
ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")"
|
||||||
ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")"
|
ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")"
|
||||||
ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")"
|
ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")"
|
||||||
test "$CI_COMMIT_TAG" = "v${VERSION_NAME}"
|
if [ -n "${CI_COMMIT_TAG:-}" ]; then
|
||||||
|
test -z "${CI_COMMIT_BRANCH:-}"
|
||||||
|
test "$CI_COMMIT_TAG" = "v${VERSION_NAME}"
|
||||||
|
test "$(git rev-parse "refs/tags/${CI_COMMIT_TAG}^{commit}")" = "$CI_COMMIT_SHA"
|
||||||
|
RELEASE_GIT_REF="refs/tags/${CI_COMMIT_TAG}"
|
||||||
|
else
|
||||||
|
test "${CI_COMMIT_BRANCH:-}" = "main"
|
||||||
|
test "$CI_COMMIT_SHA" = "$(git rev-parse origin/main)"
|
||||||
|
RELEASE_GIT_REF="refs/heads/main"
|
||||||
|
fi
|
||||||
echo "$VERSION_NAME" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
|
echo "$VERSION_NAME" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
|
||||||
echo "$VERSION_CODE" | grep -Eq '^[1-9][0-9]{0,9}$'
|
echo "$VERSION_CODE" | grep -Eq '^[1-9][0-9]{0,9}$'
|
||||||
test "$VERSION_CODE" -le 2100000000
|
test "$VERSION_CODE" -le 2100000000
|
||||||
|
|
@ -387,7 +398,7 @@ mobile-production-release:
|
||||||
--repository "$CI_PROJECT_PATH" \
|
--repository "$CI_PROJECT_PATH" \
|
||||||
--commit-sha "$CI_COMMIT_SHA" \
|
--commit-sha "$CI_COMMIT_SHA" \
|
||||||
--tree-sha "$SOURCE_TREE_SHA" \
|
--tree-sha "$SOURCE_TREE_SHA" \
|
||||||
--git-ref "refs/tags/$CI_COMMIT_TAG" \
|
--git-ref "$RELEASE_GIT_REF" \
|
||||||
--workflow-identity "gitlab-ci/mobile-production-release" \
|
--workflow-identity "gitlab-ci/mobile-production-release" \
|
||||||
--run-id "$CI_JOB_ID" \
|
--run-id "$CI_JOB_ID" \
|
||||||
--run-attempt "1" \
|
--run-attempt "1" \
|
||||||
|
|
@ -414,7 +425,7 @@ mobile-production-release:
|
||||||
--expected-repository "$CI_PROJECT_PATH" \
|
--expected-repository "$CI_PROJECT_PATH" \
|
||||||
--expected-commit-sha "$CI_COMMIT_SHA" \
|
--expected-commit-sha "$CI_COMMIT_SHA" \
|
||||||
--expected-tree-sha "$SOURCE_TREE_SHA" \
|
--expected-tree-sha "$SOURCE_TREE_SHA" \
|
||||||
--expected-git-ref "refs/tags/$CI_COMMIT_TAG" \
|
--expected-git-ref "$RELEASE_GIT_REF" \
|
||||||
--expected-workflow-identity "gitlab-ci/mobile-production-release" \
|
--expected-workflow-identity "gitlab-ci/mobile-production-release" \
|
||||||
--expected-run-id "$CI_JOB_ID" \
|
--expected-run-id "$CI_JOB_ID" \
|
||||||
--expected-run-attempt "1" \
|
--expected-run-attempt "1" \
|
||||||
|
|
@ -449,7 +460,10 @@ mobile-production-release:
|
||||||
- apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json
|
- apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json
|
||||||
manual_confirmation: '검증된 production AAB를 생성해 Maintainer 전용 Play Console handoff로 보낼까요?'
|
manual_confirmation: '검증된 production AAB를 생성해 Maintainer 전용 Play Console handoff로 보낼까요?'
|
||||||
rules:
|
rules:
|
||||||
- if: '$D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED == "true" && $D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
|
- if: '$D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED == "true" && $D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
|
||||||
|
when: manual
|
||||||
|
allow_failure: false
|
||||||
|
- if: '$D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED == "true" && $D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH == "main"'
|
||||||
when: manual
|
when: manual
|
||||||
allow_failure: false
|
allow_failure: false
|
||||||
- when: never
|
- when: never
|
||||||
|
|
|
||||||
733
docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md
Normal file
733
docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md
Normal file
|
|
@ -0,0 +1,733 @@
|
||||||
|
# D3RO Voice 모바일 출시 핸드오프 — 2026-08-29
|
||||||
|
|
||||||
|
## 0. 결론
|
||||||
|
|
||||||
|
**모바일 앱은 아직 출시 가능 상태가 아니다.** Google Play 등록정보의 텍스트,
|
||||||
|
아이콘, feature graphic 초안은 준비됐지만 production AAB, Firebase Android 설정,
|
||||||
|
upload signing, Play-signed 설치본, 실제 핵심 기능 E2E, 스토어용 앱 스크린샷이 없다.
|
||||||
|
|
||||||
|
2026-08-29 실제 `1.1.0` debug 앱을 새 Pixel 7 에뮬레이터에서 열어 보니 다음을
|
||||||
|
확인했다.
|
||||||
|
|
||||||
|
- Metro가 없으면 `Unable to load script` 오류 화면이 표시된다.
|
||||||
|
- Metro 연결 후 첫 부팅 중 빈 흰 화면이 잠시 표시됐다.
|
||||||
|
- 이후 실제 onboarding과 로그인 화면은 열렸다.
|
||||||
|
- 인증 세션이 없어 녹음·전사·AI·팀 화면에는 진입하지 못했다.
|
||||||
|
- production/Play-signed 설치본이 아니므로 이번 캡처는 Google Play 제출물이 아니다.
|
||||||
|
|
||||||
|
사용자 요청에 따라 작업은 여기서 중단했다. `/goal`은 완료로 처리하지 않는다.
|
||||||
|
|
||||||
|
## 1. 중단 상태
|
||||||
|
|
||||||
|
종료 확인일: 2026-08-29 KST
|
||||||
|
|
||||||
|
| 항목 | 종료 상태 |
|
||||||
|
| ------------------- | --------------------------------------------------------------------------- |
|
||||||
|
| 전용 캡처 AVD | `D3RO_PlayCapture_20260829` 생성됨, `emulator-5556` 종료됨 |
|
||||||
|
| 기존 AVD | `emulator-5554`만 연결 상태로 보존. 기존 `0.0.0-e2e` 데이터는 건드리지 않음 |
|
||||||
|
| Metro | `localhost:8081` listener 없음 |
|
||||||
|
| 로컬 Supabase | `supabase stop --workdir server` 완료, local data는 Docker volume에 백업됨 |
|
||||||
|
| Android 앱 프로세스 | 전용 `emulator-5556` 종료로 함께 중단 |
|
||||||
|
| Play Console | 더 이상 변경하지 않음. 기본 등록정보 페이지는 초안 상태 |
|
||||||
|
| Firebase | 프로젝트 생성 버튼을 누르지 않음. 신규 Firebase 프로젝트 없음 |
|
||||||
|
| Git | 이 핸드오프 문서 외 추가 stage/commit/push 없음 |
|
||||||
|
| Forgejo 배포 | run #46 실패 상태. 반복 push/retry하지 않음 |
|
||||||
|
|
||||||
|
## 2. 이번 세션에서 이룬 것
|
||||||
|
|
||||||
|
### 2.1 버전과 릴리즈 메타데이터
|
||||||
|
|
||||||
|
- 제품 버전: `1.1.0`
|
||||||
|
- Android versionCode: `1010001`
|
||||||
|
- iOS build number: `1010001`
|
||||||
|
- release date: `2026-08-29`
|
||||||
|
- SSOT: `release/product-version.json`
|
||||||
|
- root, desktop, mobile package version과 Android/iOS 버전 동기화 검증 GREEN
|
||||||
|
- `CHANGELOG.md`의 `1.1.0` 항목 갱신
|
||||||
|
- Android `ko-KR`/`en-US` release notes 준비:
|
||||||
|
- `apps/mobile-rn/metadata/android/ko-KR/changelogs/1010001.txt`
|
||||||
|
- `apps/mobile-rn/metadata/android/en-US/changelogs/1010001.txt`
|
||||||
|
- `npm run release:metadata` GREEN
|
||||||
|
- `npm run release:metadata:test` GREEN, fail-closed negative 6건
|
||||||
|
|
||||||
|
### 2.2 Play 등록정보 초안
|
||||||
|
|
||||||
|
Play 앱:
|
||||||
|
|
||||||
|
- 개발자 계정: 개인 Play Console 계정
|
||||||
|
- Play app ID: `4976102237698469110`
|
||||||
|
- package: `com.d3ro.voice`
|
||||||
|
- 기본 언어: `ko-KR`
|
||||||
|
- 상태: draft
|
||||||
|
|
||||||
|
저장한 초안:
|
||||||
|
|
||||||
|
- 앱 이름: `D3RO Voice`
|
||||||
|
- 짧은 설명: 42자
|
||||||
|
- 자세한 설명: 1,213자
|
||||||
|
- 유명 음성 기록·전사 앱의 정보 구조를 비교하되 문구를 복제하지 않고,
|
||||||
|
첫 효용 → 결과 중심 기능 → 요금·광고 → 개인정보 → 책임 고지 순서로 작성
|
||||||
|
- 파일: `docs/v3/play/01-store-listing.md`
|
||||||
|
|
||||||
|
중요 경계:
|
||||||
|
|
||||||
|
- 초안만 저장했다.
|
||||||
|
- 리뷰, App content, Data safety, internal/closed/production 제출은 하지 않았다.
|
||||||
|
- 휴대전화 스크린샷 영역에는 아무 이미지도 업로드하지 않았다.
|
||||||
|
|
||||||
|
### 2.3 아이콘과 feature graphic
|
||||||
|
|
||||||
|
Play icon:
|
||||||
|
|
||||||
|
- 파일: `docs/v3/play/assets/d3ro-voice-play-icon-512.png`
|
||||||
|
- 규격: 512×512 PNG
|
||||||
|
- SHA-256: `0FAE53FB40CCE4B9FB0213CBEDB4E087CA00A8F8C438D6731C5C47C4AFD096B1`
|
||||||
|
- Play Console 업로드와 preview 확인 완료
|
||||||
|
|
||||||
|
Feature graphic canonical E:
|
||||||
|
|
||||||
|
- 제출본: `docs/v3/play/assets/feature-graphic/candidate-e-generated-signal-stage.png`
|
||||||
|
- 규격: 1024×500, PNG24, sRGB, alpha 없음
|
||||||
|
- SHA-256: `A5D82B65B95027ACE184FA06A3BBDA3433995C70F064932B5733C3D3A99644D9`
|
||||||
|
- 생성 원본, prompt, 제출본, hash를 별도로 보존
|
||||||
|
- Play Console preview에서 crop·thumbnail contrast·규격 경고 없음을 확인
|
||||||
|
- 이것은 **feature graphic**이며 앱 스크린샷이 아니다.
|
||||||
|
|
||||||
|
검증:
|
||||||
|
|
||||||
|
- `npm run release:play:assets` GREEN
|
||||||
|
- `npm run release:play:assets:test` GREEN
|
||||||
|
- canonical E 변조 포함 fail-closed negative 7건 GREEN
|
||||||
|
|
||||||
|
### 2.4 Git과 CI
|
||||||
|
|
||||||
|
현재 권위 있는 SHA:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4872d6f6c50a15104792fb31a97176527c3cd9a2
|
||||||
|
```
|
||||||
|
|
||||||
|
다음 ref가 모두 위 SHA와 일치한다.
|
||||||
|
|
||||||
|
- local `main`
|
||||||
|
- `origin/main`
|
||||||
|
- `chanpaca/main`
|
||||||
|
|
||||||
|
주요 릴리즈 커밋:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4872d6f docs(release): update 1.1.0 launch metadata
|
||||||
|
0a8cf43 feat(play): finalize store graphics and copy
|
||||||
|
563d4cb fix(ci): make mobile Gradle wrapper executable
|
||||||
|
6a9608c ci: bootstrap Linux release toolchains
|
||||||
|
30d5127 build(release): forbid unsigned Windows artifacts
|
||||||
|
ec5aec7 fix(release): retire legacy capture publisher
|
||||||
|
547d4ad security: remove credential automation from releases
|
||||||
|
f342ea8 fix(release): sync legacy mobile version
|
||||||
|
3e9e04f ci(release): require trusted Windows signing
|
||||||
|
5205dcd feat(release): prepare 1.1.0 candidate
|
||||||
|
```
|
||||||
|
|
||||||
|
GitLab CI:
|
||||||
|
|
||||||
|
- 최종 pipeline: `72066`
|
||||||
|
- URL: `https://gitlab.twentyoz.kr:8443/d3r0/voice/-/pipelines/72066`
|
||||||
|
- commit: `4872d6f6`
|
||||||
|
- 결과: SUCCESS, 7/7
|
||||||
|
- GREEN jobs: lint/typecheck, unit, API, Edge, mobile-quality,
|
||||||
|
build-workspaces, mobile-android
|
||||||
|
- 이전 graphics/copy pipeline `72065`도 SUCCESS, 7/7
|
||||||
|
|
||||||
|
주의: CI GREEN은 production AAB, Play 설치, 실제 provider, 스크린샷 검증을
|
||||||
|
대체하지 않는다.
|
||||||
|
|
||||||
|
### 2.5 실제 앱 실행 시도
|
||||||
|
|
||||||
|
새 전용 AVD:
|
||||||
|
|
||||||
|
- 이름: `D3RO_PlayCapture_20260829`
|
||||||
|
- device profile: Pixel 7
|
||||||
|
- system image: Android 34 Google APIs Play Store x86_64
|
||||||
|
- serial: 실행 시 `emulator-5556`
|
||||||
|
- 원래 해상도: 1080×2400
|
||||||
|
- 캡처 시 임시 override: 1080×1920
|
||||||
|
- 현재 상태: 종료됨
|
||||||
|
|
||||||
|
설치한 실제 debug APK:
|
||||||
|
|
||||||
|
- 파일: `apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk`
|
||||||
|
- package: `com.d3ro.voice`
|
||||||
|
- label: `D3RO Voice`
|
||||||
|
- versionName: `1.1.0`
|
||||||
|
- versionCode: `1010001`
|
||||||
|
- targetSdk: 36
|
||||||
|
- SHA-256: `7AD0FD0716D8F900053B31D72C53ADC035889D8B507D95602FEC49F5A5C78B87`
|
||||||
|
- signer: Android Debug
|
||||||
|
- debug signer SHA-256:
|
||||||
|
`FAC61745DC0903786FB9EDE62A962B399F7348F0BB6F899B8332667591033B9C`
|
||||||
|
|
||||||
|
정상 확인:
|
||||||
|
|
||||||
|
- 새 AVD 설치 성공
|
||||||
|
- cold start가 `com.d3ro.voice/.MainActivity`로 실행됨
|
||||||
|
- Metro 연결 후 React Native `D3ROVoice` root 실행 확인
|
||||||
|
- 실제 onboarding과 login UI 확인
|
||||||
|
- Keychain이 빈 새 설치에서 인증 세션 없음 확인
|
||||||
|
|
||||||
|
## 3. 하지 못한 것
|
||||||
|
|
||||||
|
### 3.1 실제 핵심 기능 검증
|
||||||
|
|
||||||
|
다음 화면은 실제 앱에서 열지 못했다.
|
||||||
|
|
||||||
|
- 녹음 ready/recording/paused 상태
|
||||||
|
- 실제 WAV 생성과 foreground notification
|
||||||
|
- 오디오/MP4 가져오기
|
||||||
|
- 실제 전사 결과와 history detail
|
||||||
|
- Talk AI 응답과 신고 sheet
|
||||||
|
- 생성 문서/회의록
|
||||||
|
- 팀/초대/역할 화면
|
||||||
|
- 개인정보, export/restore, account deletion의 authenticated 경로
|
||||||
|
- 광고 동의·배너·rewarded SSV
|
||||||
|
- Play Billing purchase/restore
|
||||||
|
|
||||||
|
원인:
|
||||||
|
|
||||||
|
- 새 debug 설치에는 로그인 세션이 없다.
|
||||||
|
- debug 앱은 production Supabase를 사용한다.
|
||||||
|
- 안전한 인증 우회나 기능 화면 deep link가 없다.
|
||||||
|
- auth session은 Android Keychain에 암호화되므로 AsyncStorage/adb seed로 우회할 수 없다.
|
||||||
|
- 사용자의 실제 계정을 캡처에 사용하지 않았다.
|
||||||
|
- 외부 disposable 계정도 새로 만들지 않았다.
|
||||||
|
|
||||||
|
### 3.2 production Android release
|
||||||
|
|
||||||
|
아직 없는 것:
|
||||||
|
|
||||||
|
- Firebase project와 Android app
|
||||||
|
- `google-services.json`
|
||||||
|
- production upload keystore path/alias/password CI 설정
|
||||||
|
- release evidence private key CI 설정
|
||||||
|
- Firebase project ID/number/mobile SDK app ID CI 설정
|
||||||
|
- `D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED=true`
|
||||||
|
- Android emulator runner production gate
|
||||||
|
- production signed AAB
|
||||||
|
- `v1.1.0` tag
|
||||||
|
- Play internal track upload
|
||||||
|
- Play-signed install
|
||||||
|
- App Bundle Explorer/signing evidence
|
||||||
|
|
||||||
|
production AAB는 생성되지 않았다. self-signed, debug, E2E 또는 출처 불명
|
||||||
|
keystore를 사용하지 않았다.
|
||||||
|
|
||||||
|
### 3.3 Google Play 스크린샷
|
||||||
|
|
||||||
|
현재 store-ready 휴대전화 스크린샷 수: **0장**
|
||||||
|
|
||||||
|
- Play Console screenshot slot은 비어 있다.
|
||||||
|
- 최소 2장, 권장 4장 이상이 필요하다.
|
||||||
|
- 실제 핵심 기능 화면을 아직 열지 못했으므로 업로드하지 않았다.
|
||||||
|
|
||||||
|
### 3.4 Firebase
|
||||||
|
|
||||||
|
생성 예정으로 준비했던 값:
|
||||||
|
|
||||||
|
- project name: `D3RO Voice Production`
|
||||||
|
- project ID: `d3ro-voice-production`
|
||||||
|
- Android package: `com.d3ro.voice`
|
||||||
|
- Developer Program: off
|
||||||
|
- Gemini: off
|
||||||
|
- Analytics: off
|
||||||
|
|
||||||
|
하지만 최종 `프로젝트 만들기` 버튼은 누르지 않았다. Firebase project/API
|
||||||
|
identity는 생성되지 않았다. 내일 action-time 승인 후 다시 시작해야 한다.
|
||||||
|
|
||||||
|
### 3.5 공개 사이트 배포
|
||||||
|
|
||||||
|
Forgejo push 자체는 완료됐다.
|
||||||
|
|
||||||
|
- `chanpaca/main`: `4872d6f6c50a15104792fb31a97176527c3cd9a2`
|
||||||
|
- sparse pack 문제를 비활성화해 136MiB 전송을 3.66MiB로 줄인 뒤 push 성공
|
||||||
|
|
||||||
|
하지만 배포는 실패했다.
|
||||||
|
|
||||||
|
- Forgejo deploy-site run: `#46`
|
||||||
|
- 결과: FAILURE
|
||||||
|
- 실패 위치: `Set up job`
|
||||||
|
- 경과: 10분 53초
|
||||||
|
- checkout/build/Cloudflare/verify: 모두 0초, 시작되지 않음
|
||||||
|
- 원인: `linux-builder` runner가 job을 claim/initialize하지 못해 timeout
|
||||||
|
|
||||||
|
현재 public 상태는 최신 배포가 아니다.
|
||||||
|
|
||||||
|
- `/release-identity.json`: 404
|
||||||
|
- root `/`: 404
|
||||||
|
- live `assetlinks.json`: 과거 인증서 SHA 유지
|
||||||
|
- live download page: 과거 `v1.0.0 STABLE` / `Signed & Verified` 문구 유지
|
||||||
|
- privacy/terms/delete-account: 기존 공개 페이지는 접근 가능
|
||||||
|
|
||||||
|
새 commit이나 반복 push로 해결할 문제가 아니다. runner 복구 후 run #46만
|
||||||
|
재실행하고 public exact verification을 해야 한다.
|
||||||
|
|
||||||
|
### 3.6 Windows 공개 릴리즈
|
||||||
|
|
||||||
|
- 로컬 installer는 Authenticode `NotSigned`
|
||||||
|
- public-trust code-signing certificate/PFX가 없음
|
||||||
|
- 기존 self-signed `CN=Everything2EverythingDev`는 public release에 사용 금지
|
||||||
|
- stable `v1.1.0` tag/release와 updater publish는 계속 차단
|
||||||
|
|
||||||
|
## 4. 새로 발견된 것
|
||||||
|
|
||||||
|
### 4.1 설치돼 있던 앱은 production 앱이 아니었다
|
||||||
|
|
||||||
|
기존 `emulator-5554`의 `com.d3ro.voice`:
|
||||||
|
|
||||||
|
- versionName: `0.0.0-e2e.20260829.1`
|
||||||
|
- versionCode: `202608291`
|
||||||
|
- installer: `null`
|
||||||
|
- microphone/notification permission: 미허용
|
||||||
|
|
||||||
|
기존 상태를 캡처해 Play 증거로 쓰면 안 된다. 기존 에뮬레이터는 보존했다.
|
||||||
|
|
||||||
|
### 4.2 debug APK는 Metro 없이 standalone 실행되지 않는다
|
||||||
|
|
||||||
|
`app-debug.apk` 첫 cold start에서 Metro가 없으면 다음 오류가 실제로 표시됐다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Unable to load script.
|
||||||
|
Make sure you're running Metro or that your bundle 'index.android.bundle'
|
||||||
|
is packaged correctly for release.
|
||||||
|
```
|
||||||
|
|
||||||
|
이는 debug variant의 예상 개발 경계이지만, source/build GREEN만으로 앱을
|
||||||
|
열었다고 판단하면 안 된다는 실증이다. 최종 Play 캡처는 embedded bundle을 가진
|
||||||
|
production/Play-signed 설치본이어야 한다.
|
||||||
|
|
||||||
|
### 4.3 Metro 연결 직후 빈 흰 화면이 관찰됐다
|
||||||
|
|
||||||
|
Metro bundle 로딩 뒤 첫 캡처는 status/navigation bar 외 전부 흰 화면이었다.
|
||||||
|
수 초 뒤 onboarding이 표시됐다. production build에서 다음을 별도로 검증해야 한다.
|
||||||
|
|
||||||
|
- cold start splash/boot feedback
|
||||||
|
- blank frame 지속 시간
|
||||||
|
- provider/auth 초기화 실패 시 fail-closed UI
|
||||||
|
- offline/slow network 시작 경로
|
||||||
|
- process death 후 recovery
|
||||||
|
|
||||||
|
### 4.4 실제 onboarding은 열렸지만 핵심 기능은 인증 뒤에 있다
|
||||||
|
|
||||||
|
실제 onboarding과 signup/login 화면은 정상 렌더됐다. 그러나 debug 앱에는
|
||||||
|
안전한 local/demo 기능 route가 없고 인증 우회도 없다. local disposable
|
||||||
|
Supabase + E2E APK를 사용하면 기능 UI를 열 수 있지만 그 결과는 TEST-DEMO이며
|
||||||
|
Play-signed production 증거가 아니다.
|
||||||
|
|
||||||
|
### 4.5 캡처용 해상도를 별도로 고정해야 한다
|
||||||
|
|
||||||
|
Pixel 7 AVD 기본 캡처는 1080×2400이었다. Play draft 요구와 일관된 세로 캡처를
|
||||||
|
위해 dedicated AVD에 `wm size 1080x1920`을 적용했다. 내일 재개 시 실제 값을
|
||||||
|
다시 확인하고, Play-signed 설치본에서도 동일 viewport가 제품 UI를 자르거나
|
||||||
|
왜곡하지 않는지 검증해야 한다.
|
||||||
|
|
||||||
|
### 4.6 작업 트리가 매우 dirty하다
|
||||||
|
|
||||||
|
중단 시 `git status --short` 집계:
|
||||||
|
|
||||||
|
- total: 197
|
||||||
|
- modified: 55
|
||||||
|
- deleted: 17
|
||||||
|
- untracked: 125
|
||||||
|
|
||||||
|
대부분 기존 build/test/generated/user 작업이다. 전체 stage, 전체 commit,
|
||||||
|
reset, restore, clean을 금지한다. 이번에 새로 생긴 캡처 디렉터리만 별도로
|
||||||
|
식별됐다.
|
||||||
|
|
||||||
|
## 5. 캡처 증거 원장
|
||||||
|
|
||||||
|
디렉터리:
|
||||||
|
|
||||||
|
`docs/v3/play/assets/screenshots/1.1.0-debug-capture-20260829/`
|
||||||
|
|
||||||
|
| 파일 | 규격 | SHA-256 | 내용 | 판정 |
|
||||||
|
| ------------------------ | --------- | ------------------------------------------------------------------ | ----------------------------------------- | ------------------------------------------------- |
|
||||||
|
| `00-launch.png` | 1080×2400 | `4818E5266471D07A63B0162D60B673354E17DF8BD0F557FE0ADDF9E04EE94711` | Metro 미연결 `Unable to load script` 오류 | **폐기**, 오류 증거만 보존 |
|
||||||
|
| `01-runtime.png` | 1080×1920 | `89947F52FE4CA38A2729D1C706CAB5C0AD87A45405843F4769D0ABDFD4F440CC` | Metro 연결 직후 빈 흰 화면 | **폐기**, boot 문제 증거만 보존 |
|
||||||
|
| `02-after-bootstrap.png` | 1080×1920 | `95AC6B0B2792086DE2D38AE719FBED222A3595158A2C9F37403E31F756FD6FC8` | 실제 `1.1.0` onboarding 1/5 | 실제 앱 화면이지만 **debug이며 store-ready 아님** |
|
||||||
|
|
||||||
|
세 파일 모두 Play Console에 업로드하지 않는다. `02-after-bootstrap.png`도
|
||||||
|
production/Play-signed 재촬영 전에는 참고 증거일 뿐이다.
|
||||||
|
|
||||||
|
## 6. 내일 진행해야 할 것
|
||||||
|
|
||||||
|
### P0 — 앱을 먼저 검증한다
|
||||||
|
|
||||||
|
1. 아래 `재개 전 사실 확인` 명령을 실행한다.
|
||||||
|
2. Firebase project/app 생성에 대한 action-time 승인을 다시 받는다.
|
||||||
|
3. Firebase Android app `com.d3ro.voice`를 등록한다.
|
||||||
|
4. Play app-signing SHA-1/SHA-256과 upload key SHA를 Firebase에 정확히 등록한다.
|
||||||
|
5. `google-services.json`을 내려받아 package/project identity를 검증한다.
|
||||||
|
6. public-trust와 별개인 Android upload key를 기대 SHA와 대조한다.
|
||||||
|
7. GitLab protected/masked/hidden CI variables를 구성한다.
|
||||||
|
8. `mobile-production-release` job으로 production AAB를 만든다.
|
||||||
|
9. AAB version/package/SDK/merged manifest/dependency/signature/provenance를 검증한다.
|
||||||
|
10. 사용자 확인 후 Play internal track에 AAB를 업로드한다.
|
||||||
|
11. Play가 서명한 설치본을 clean install한다.
|
||||||
|
12. 로그인, 녹음, 전사, AI, 팀, 신고, 광고, Billing, 삭제를 실제로 검증한다.
|
||||||
|
|
||||||
|
### P0 — 실제 스토어 스크린샷을 만든다
|
||||||
|
|
||||||
|
최종 캡처는 Play-signed 설치본과 dedicated reviewer/capture 계정을 사용한다.
|
||||||
|
사용자의 실제 이메일, 회의, 음성, 팀, 토큰을 사용하지 않는다.
|
||||||
|
|
||||||
|
권장 6장 흐름:
|
||||||
|
|
||||||
|
1. **녹음** — 녹음 준비 또는 실제 recording 상태, 명확한 controls와 상태 표시
|
||||||
|
2. **전사 결과** — 실제 WAV에서 생성된 transcript/history detail
|
||||||
|
3. **AI 문서화** — 실제 transcript 기반 요약/문서와 책임 있는 사용 경계
|
||||||
|
4. **Talk + 신고** — 실제 AI 응답과 사용자가 접근 가능한 report affordance
|
||||||
|
5. **작업/팀** — Works hub 또는 팀 협업의 실제 접근 가능한 구조
|
||||||
|
6. **데이터 관리** — privacy, export/restore, device/account deletion 경로
|
||||||
|
|
||||||
|
각 이미지 acceptance criteria:
|
||||||
|
|
||||||
|
- 1080×1920 PNG 또는 Console이 수락하는 검증된 실제 device ratio
|
||||||
|
- package `com.d3ro.voice`
|
||||||
|
- versionName `1.1.0`, versionCode `1010001`
|
||||||
|
- installer `com.android.vending`
|
||||||
|
- Play app-signing certificate SHA-256이 기대값과 일치
|
||||||
|
- DEBUGGABLE 아님
|
||||||
|
- test AdMob label/fixture/debug menu/error/red screen/blank screen 없음
|
||||||
|
- 생성형 mock UI 없음
|
||||||
|
- 실사용자 PII, reviewer credential, token, 실제 회의 내용 없음
|
||||||
|
- 화면의 모든 기능이 같은 Play 설치본에서 실제 접근 가능
|
||||||
|
- crop/자막을 쓰더라도 실제 UI를 가리거나 기능을 과장하지 않음
|
||||||
|
|
||||||
|
### P1 — Forgejo 공개 배포 복구
|
||||||
|
|
||||||
|
1. `linux-builder` runner service/registration/labels/capacity를 복구한다.
|
||||||
|
2. 새 commit이나 push를 만들지 않는다.
|
||||||
|
3. run #46만 재실행한다.
|
||||||
|
4. public `release-identity.json`의 commit이
|
||||||
|
`4872d6f6c50a15104792fb31a97176527c3cd9a2`인지 확인한다.
|
||||||
|
5. version이 `1.1.0`인지 확인한다.
|
||||||
|
6. root/privacy/terms/delete-account가 HTTPS 200인지 확인한다.
|
||||||
|
7. download page에서 stale `v1.0.0 STABLE`과 unsigned `Signed & Verified`
|
||||||
|
문구가 사라졌는지 확인한다.
|
||||||
|
8. `assetlinks.json`이 Play app-signing SHA만 포함하고 upload SHA를 포함하지
|
||||||
|
않는지 확인한다.
|
||||||
|
|
||||||
|
### P1 — Windows 공개 릴리즈
|
||||||
|
|
||||||
|
1. public-trust Windows code-signing certificate를 준비한다.
|
||||||
|
2. expected signer subject를 CI secret와 gate에 설정한다.
|
||||||
|
3. installer와 unpacked exe 모두 Authenticode `Valid`를 검증한다.
|
||||||
|
4. 설치된 앱, registry, updater feed, visible version까지 E2E한다.
|
||||||
|
5. 그 이후에만 `v1.1.0` tag/release/updater publish를 진행한다.
|
||||||
|
|
||||||
|
## 7. 재개 전 사실 확인
|
||||||
|
|
||||||
|
저장소 루트 PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git status --short
|
||||||
|
git rev-parse HEAD
|
||||||
|
git rev-parse origin/main
|
||||||
|
git rev-parse chanpaca/main
|
||||||
|
git ls-remote origin refs/heads/main
|
||||||
|
git ls-remote chanpaca refs/heads/main
|
||||||
|
adb devices -l
|
||||||
|
Get-NetTCPConnection -LocalPort 8081 -State Listen -ErrorAction SilentlyContinue
|
||||||
|
docker ps --format '{{.Names}}' | Select-String 'supabase_.*d3ro-voice'
|
||||||
|
npm run release:metadata
|
||||||
|
npm run release:play:assets
|
||||||
|
npm run release:mobile:boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
기대 기준:
|
||||||
|
|
||||||
|
- 세 Git SHA가 모두 `4872d6f6c50a15104792fb31a97176527c3cd9a2`
|
||||||
|
- 기존 `emulator-5554` 외 새 캡처 emulator는 자동으로 실행돼 있지 않음
|
||||||
|
- Metro 8081 listener 없음
|
||||||
|
- local Supabase containers 없음
|
||||||
|
- release metadata/assets/boundary GREEN
|
||||||
|
|
||||||
|
## 8. 캡처 AVD 재개 명령
|
||||||
|
|
||||||
|
이 명령은 **debug 진단용**이다. 최종 스토어 캡처는 Play-signed 설치본으로 한다.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$emulatorExe = "$env:ANDROID_SDK_ROOT\emulator\emulator.exe"
|
||||||
|
Start-Process -FilePath $emulatorExe -ArgumentList @(
|
||||||
|
'-avd', 'D3RO_PlayCapture_20260829',
|
||||||
|
'-port', '5556',
|
||||||
|
'-no-snapshot-save',
|
||||||
|
'-no-boot-anim',
|
||||||
|
'-gpu', 'auto'
|
||||||
|
)
|
||||||
|
|
||||||
|
adb -s emulator-5556 wait-for-device
|
||||||
|
adb -s emulator-5556 shell getprop sys.boot_completed
|
||||||
|
adb -s emulator-5556 shell wm size 1080x1920
|
||||||
|
```
|
||||||
|
|
||||||
|
Debug + Metro가 필요할 때만:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-Location apps/mobile-rn
|
||||||
|
npm run start -- --port 8081
|
||||||
|
adb -s emulator-5556 reverse tcp:8081 tcp:8081
|
||||||
|
adb -s emulator-5556 install -r android/app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
adb -s emulator-5556 shell am start -W -n com.d3ro.voice/.MainActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
실제 화면 캡처 기본형:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
adb -s emulator-5556 shell screencap -p /sdcard/d3ro-capture.png
|
||||||
|
adb -s emulator-5556 pull /sdcard/d3ro-capture.png <VERIFIED_OUTPUT_PATH>
|
||||||
|
```
|
||||||
|
|
||||||
|
중단:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
adb -s emulator-5556 emu kill
|
||||||
|
supabase stop --workdir D:\workspace\D3ROVoice\server
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 인증서와 identity 기준
|
||||||
|
|
||||||
|
Play app-signing SHA-256:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Upload key SHA-256:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
두 값은 서로 달라야 한다. Firebase에는 Google/FCM/auth 요구에 맞춰 필요한
|
||||||
|
Play app-signing 및 upload certificate fingerprint를 명확히 구분해 등록한다.
|
||||||
|
`assetlinks.json`에는 Play가 최종 사용자 APK에 적용하는 app-signing SHA만 둔다.
|
||||||
|
|
||||||
|
## 10. 외부 변경과 확인 경계
|
||||||
|
|
||||||
|
다음은 action-time 사용자 확인 후에만 한다.
|
||||||
|
|
||||||
|
- Firebase project 최종 생성
|
||||||
|
- Firebase Android app 등록과 persistent API identity 생성
|
||||||
|
- GitLab secret/protected variable 생성·변경
|
||||||
|
- Play internal/closed/production track AAB 업로드와 저장
|
||||||
|
- App content, Data safety, 등급, 금융 기능, FGS, AI/UGC 선언 저장
|
||||||
|
- reviewer credential 입력
|
||||||
|
- merchant/payment/tax profile 변경
|
||||||
|
- production review 제출·staged rollout
|
||||||
|
- public tag/release/updater publish
|
||||||
|
- cloud account/project/API key 삭제
|
||||||
|
|
||||||
|
## 11. 금지 사항
|
||||||
|
|
||||||
|
- `00-launch.png`, `01-runtime.png`, `02-after-bootstrap.png`를 Play 최종
|
||||||
|
스크린샷으로 업로드하지 않는다.
|
||||||
|
- feature graphic을 앱 스크린샷이라고 부르지 않는다.
|
||||||
|
- 생성 이미지, mock UI, 로그인/초대 failure evidence를 제품 스크린샷으로 쓰지 않는다.
|
||||||
|
- debug/E2E APK, sideload installer, self-signed certificate를 production 증거로 쓰지 않는다.
|
||||||
|
- 실제 사용자의 이메일, 토큰, 음성, 전사, 회의, 팀을 캡처에 노출하지 않는다.
|
||||||
|
- unknown/nontracked keystore를 비밀번호 추측으로 사용하지 않는다.
|
||||||
|
- public-trust Windows certificate 없이 stable tag/release를 만들지 않는다.
|
||||||
|
- Forgejo runner failure에 새 commit/push를 반복하지 않는다.
|
||||||
|
- dirty worktree 전체를 stage/commit/reset/restore/clean하지 않는다.
|
||||||
|
- legal/Data safety/production 제출을 자동 진행하지 않는다.
|
||||||
|
|
||||||
|
## 12. 소스 오브 트루스
|
||||||
|
|
||||||
|
- 모바일 완료 상태: `docs/v3/MOBILE_APP_COMPLETION_SSOT.md`
|
||||||
|
- 이전 제품화 핸드오프: `docs/v3/MOBILE_PRODUCTIZATION_HANDOFF_2026-08-21.md`
|
||||||
|
- Play 패키지 안내: `docs/v3/play/README.md`
|
||||||
|
- Play 등록정보: `docs/v3/play/01-store-listing.md`
|
||||||
|
- Data safety 초안: `docs/v3/play/02-data-safety-draft.md`
|
||||||
|
- App content 초안: `docs/v3/play/03-app-content-draft.md`
|
||||||
|
- 출시 체크리스트: `docs/v3/play/04-release-checklist.md`
|
||||||
|
- 신고 운영: `docs/v3/play/05-content-reporting-operations.md`
|
||||||
|
- version SSOT: `release/product-version.json`
|
||||||
|
- Android release identity: `release/android-release-identity.json`
|
||||||
|
- CI production job: `.gitlab-ci.yml`의 `mobile-production-release`
|
||||||
|
- Play asset verifier: `scripts/ci/verify-play-store-assets.mjs`
|
||||||
|
- release metadata verifier: `scripts/ci/verify-release-metadata.mjs`
|
||||||
|
- mobile boundary verifier: `scripts/ci/verify-mobile-release-boundary.mjs`
|
||||||
|
|
||||||
|
## 13. 내일 첫 체크포인트
|
||||||
|
|
||||||
|
내일은 Play Console부터 만지지 않는다. 다음 순서로 시작한다.
|
||||||
|
|
||||||
|
1. Git/프로세스/AVD/local stack 상태 재확인
|
||||||
|
2. 이 문서의 `새로 발견된 것`을 재현 가능한 issue로 분리
|
||||||
|
3. production-like cold start에서 red screen/blank screen이 없는지 먼저 검증
|
||||||
|
4. Firebase/signing/AAB gate를 닫음
|
||||||
|
5. Play-signed install에서 핵심 기능 E2E
|
||||||
|
6. 그 뒤 실제 스토어 스크린샷 촬영과 Play draft 업로드
|
||||||
|
|
||||||
|
이 순서를 바꾸지 않는다.
|
||||||
|
|
||||||
|
## 14. 2026-08-30 재개 감사
|
||||||
|
|
||||||
|
이 절은 2026-08-29의 중단 스냅샷을 지우지 않고, 다음 날 다시 확인한 현재
|
||||||
|
상태와 새 작업을 추가한다. 이 절의 사실이 앞 절의 역사적 상태와 충돌하면 이
|
||||||
|
절을 우선한다.
|
||||||
|
|
||||||
|
### 14.1 현재 상태 재검증
|
||||||
|
|
||||||
|
- local `main`, `HEAD`, local `origin/main`, local `chanpaca/main`, 실제
|
||||||
|
`origin/main`은 계속
|
||||||
|
`4872d6f6c50a15104792fb31a97176527c3cd9a2`다.
|
||||||
|
- 실제 `chanpaca/main` 조회와 Forgejo public/LAN API, run #46 API는 timeout이라
|
||||||
|
현재 원격 상태를 확정하지 못했다. remote-tracking ref만으로 최신 원격 상태를
|
||||||
|
주장하지 않는다.
|
||||||
|
- 작업 트리는 198개 변경이며 staged는 0개다. 2026-08-29의 197개에 이 핸드오프
|
||||||
|
문서 자체가 untracked로 추가된 수치다. 전체 stage/reset/restore/clean 금지는
|
||||||
|
그대로다.
|
||||||
|
- Android emulator와 emulator/qemu 프로세스는 0개다. 전용
|
||||||
|
`D3RO_PlayCapture_20260829` AVD와 userdata는 디스크에 남아 있다.
|
||||||
|
`emulator-5554`는 현재 연결돼 있지 않으므로 앞 절의 "연결 상태로 보존"은
|
||||||
|
2026-08-29 종료 시점의 역사적 사실로만 본다.
|
||||||
|
- Fold6 무선 ADB endpoint가 두 줄로 보이지만 같은 물리기기다. 이를 두 대의
|
||||||
|
기기 증거로 세지 않는다.
|
||||||
|
- Metro 8081 listener와 D3ROVoice Supabase container는 없다. DB/storage/
|
||||||
|
edge-runtime Docker volume은 남아 있지만 이번 감사에서 내용 무결성까지 다시
|
||||||
|
검증한 것은 아니다.
|
||||||
|
- Firebase CLI와 GCP에서 exact `d3ro-voice-production` project는 없거나 현재
|
||||||
|
계정에서 접근 불가임을 확인했다. Firebase project 생성은 여전히 action-time
|
||||||
|
승인 대상이다.
|
||||||
|
- 현재 인앱 Play Console 세션은 `TWENTYOZ` 조직 계정이며 6개 앱 목록에
|
||||||
|
`com.d3ro.voice`와 Play app ID `4976102237698469110`이 없다. 이 계정에서는
|
||||||
|
변경하지 않았다. Chrome 세션도 올바른 개인 개발자 계정으로 연결돼 있지 않다.
|
||||||
|
다음 단계 전에 해당 앱을 보유한 개인 Play 계정 로그인이 필요하다.
|
||||||
|
- GitLab browser 세션은 로그인돼 있지 않고 저장된 Git credential로 variables API
|
||||||
|
조회도 401이었다. 따라서 production CI variable 존재 여부를 추측하지 않는다.
|
||||||
|
|
||||||
|
### 14.2 production-like cold start 진단
|
||||||
|
|
||||||
|
기존 `app-e2e.apk`를 전용 `emulator-5556`에 clean install해 Metro와 local
|
||||||
|
Supabase 없이 cold start했다.
|
||||||
|
|
||||||
|
- artifact: `apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk`
|
||||||
|
- version: `0.0.0-e2e.20260829.1` / `202608291`
|
||||||
|
- SHA-256: `0D20ABB8C87B088C308E2379A91448F051C13B3778C1D79962C5D004B0F5BABE`
|
||||||
|
- `DEBUGGABLE=false`, `assets/index.android.bundle` 포함
|
||||||
|
- signer: Android Debug
|
||||||
|
- 판정: embedded-bundle 시작 진단용 TEST-DEMO이며 production/Play 증거가 아님
|
||||||
|
|
||||||
|
진단 원본은 `scratch/mobile-release-coldstart-20260830/`에 둔다.
|
||||||
|
|
||||||
|
| 프레임 | SHA-256 | 관찰 |
|
||||||
|
| ------------------- | ------------------------------------------------------------------ | ------------------------------------------ |
|
||||||
|
| `frame-00330ms.png` | `0D718DB0947DD7CDD598C13DA36EF06440A12ED418B9ACABE28806832678C2AC` | Android launch splash와 실제 launcher icon |
|
||||||
|
| `frame-01828ms.png` | `163BC50C7AA0EB8227E7803719454256B5255359A5CB2FD6E2326878B26D23CA` | 앱 bootstrap spinner |
|
||||||
|
| `frame-02526ms.png` | `3A9C6F58B2CD0FBA33D39074D659FEB6A4B2807FF8E6CD9BD8EE7DD0CBE2B624` | onboarding 1/5 표시 |
|
||||||
|
| `frame-08003ms.png` | `DE53F4EF82F6D1714E9D13CA2B02104FE3F4C71015C3F689ED4DE3CDFF924BAA` | 같은 onboarding이 안정적으로 유지됨 |
|
||||||
|
|
||||||
|
Android log의 `Displayed com.d3ro.voice/.MainActivity`는 `+1s606ms`였다. fatal
|
||||||
|
exception, red screen, `Unable to load script`, 흰 화면 고착은 재현되지 않았다.
|
||||||
|
Firebase 미구성 경고와 새 설치의 Keychain entry 없음 로그는 남았으며 production
|
||||||
|
Firebase 설치본에서 다시 확인해야 한다. 진단 뒤 `emulator-5556`을 다시 종료했고
|
||||||
|
Metro/Supabase는 시작하지 않았다.
|
||||||
|
|
||||||
|
따라서 2026-08-29의 빈 흰 화면은 현재까지 debug+Metro bootstrap에서 본
|
||||||
|
증거다. embedded bundle 경로의 영구 결함으로 일반화하지 않는다. production
|
||||||
|
Play-signed 설치본의 cold start 검증은 여전히 RED다.
|
||||||
|
|
||||||
|
### 14.3 새로 확인한 release artifact 경계
|
||||||
|
|
||||||
|
- 저장소 어디에도 production AAB가 없다.
|
||||||
|
- 저장소/CI 작업 디렉터리에 materialize된 `google-services.json`,
|
||||||
|
`android/app/release.keystore`, release evidence private key, release
|
||||||
|
snapshot/publication은 없다.
|
||||||
|
- 저장소 밖 upload PKCS12와 Ed25519 evidence private key는 실제로 남아 있다.
|
||||||
|
`verify-windows-android-upload-key.ps1`은 alias
|
||||||
|
`d3ro-upload-20260821`, 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`,
|
||||||
|
private-key readback과 Credential Manager target을 다시 GREEN으로 확인했다.
|
||||||
|
evidence keypair도 keyId
|
||||||
|
`2797d3e63affd2348941bc2871b57e520c958f7f5da1a4bbe8aa46904a890b7f`와
|
||||||
|
sign/verify roundtrip이 GREEN이다. 없는 것은 CI secret materialization,
|
||||||
|
offline 복구 백업, 실제 AAB signer/evidence다.
|
||||||
|
- `npm run release:mobile:config`는 실제 production config가 없으므로
|
||||||
|
`google_services_file_unreadable`로 fail-closed한다.
|
||||||
|
- 기존 `android/app/build/outputs/apk/release/app-release.apk`는 2026-08-20의
|
||||||
|
오래된 `1.0/1`이다. SHA-256은
|
||||||
|
`8530D5ABEE72DB00E332B1362D5B3568CFBD080405D15DC27125F66E18A354B3`, signer
|
||||||
|
SHA-256은 `06EEC757...2E481`이며 현재 verifier가 차단한다. 필수 Whisper
|
||||||
|
model도 없어 `missing_entry_assets/models/ggml-tiny.bin`으로 실패한다.
|
||||||
|
절대 재사용하지 않는다.
|
||||||
|
- local Node는 `.nvmrc`의 `24.19.0`이 아니라 `23.4.0`이다. production artifact는
|
||||||
|
이 dirty local checkout에서 만들지 않고 고정된 CI image/toolchain에서 만든다.
|
||||||
|
|
||||||
|
### 14.4 AAB/tag 교착의 소스 수정
|
||||||
|
|
||||||
|
기존 GitLab job은 exact stable tag가 있어야만 production AAB를 만들 수 있었지만,
|
||||||
|
이 문서는 Windows public-trust signing 전 stable tag를 금지했다. 이 순서는 실행
|
||||||
|
불가능했다.
|
||||||
|
|
||||||
|
다음 두 파일을 좁게 수정했다.
|
||||||
|
|
||||||
|
- `.gitlab-ci.yml`
|
||||||
|
- `scripts/ci/verify-mobile-release-boundary.mjs`
|
||||||
|
|
||||||
|
새 계약:
|
||||||
|
|
||||||
|
- protected exact `main`에서는 `mobile-production-release`를 수동 실행해
|
||||||
|
Maintainer 전용 candidate AAB/evidence만 만든다.
|
||||||
|
- branch candidate evidence ref는 `refs/heads/main`이다.
|
||||||
|
- exact stable semver tag 경로는 계속 지원하며 tag evidence ref를 쓴다.
|
||||||
|
- `publish-release`는 계속 tag-only다. main candidate가 public release를
|
||||||
|
트리거하지 않는다.
|
||||||
|
|
||||||
|
mobile boundary self-test, release metadata self-test, YAML parser 2종, job shell
|
||||||
|
syntax, `git diff --check`는 GREEN이다. 두 코드 파일은 staged/committed/pushed되지
|
||||||
|
않았고 이 핸드오프 문서도 untracked다. 사용자 확인 후 이 세 파일만 좁게
|
||||||
|
commit/push해야 실제 GitLab candidate job을 사용할 수 있다.
|
||||||
|
|
||||||
|
### 14.5 Play-signed 캡처 기기 교착
|
||||||
|
|
||||||
|
현재 production release 계약은 `arm64-v8a`만 허용하지만 전용 Pixel 7 AVD는
|
||||||
|
`x86_64`다. 따라서 현 AAB의 Play-signed APK를 이 AVD에 설치할 수 있다고
|
||||||
|
가정하면 안 된다.
|
||||||
|
|
||||||
|
Google Play의 현재 공식 요구는 휴대전화 screenshot의 최소 변 320px, 최대 변
|
||||||
|
3840px, 긴 변이 짧은 변의 2배 이하이며, 추천 노출은 1080×1920 이상의 9:16
|
||||||
|
세로 이미지 4장 이상이다. 실제 앱 경험을 보여야 한다.
|
||||||
|
|
||||||
|
- 규격 근거:
|
||||||
|
`https://support.google.com/googleplay/android-developer/answer/9866151`
|
||||||
|
- Play-signed universal/device APK 근거:
|
||||||
|
`https://support.google.com/googleplay/android-developer/answer/9844279`
|
||||||
|
|
||||||
|
최종 선택지는 다음 중 하나를 production AAB 전에 확정한다.
|
||||||
|
|
||||||
|
1. arm64 Fold6에 Play internal 설치 후 실제 UI를 왜곡하지 않는 9:16 store
|
||||||
|
canvas로 구성하고 Console 수락을 검증한다.
|
||||||
|
2. release AAB에 검증된 `x86_64` 지원을 추가하고 verifier/CI/실기기 matrix를
|
||||||
|
함께 확장한 뒤 전용 AVD에서 Play 설치를 검증한다.
|
||||||
|
|
||||||
|
단순 crop/stretch, debug/E2E 재촬영, installer/certificate 검증 없는 sideload는
|
||||||
|
대안이 아니다.
|
||||||
|
|
||||||
|
### 14.6 현재 재개 순서와 승인 요청
|
||||||
|
|
||||||
|
1. 위 두 CI 파일과 이 핸드오프 문서의 좁은 commit/push를 승인받는다.
|
||||||
|
2. Firebase project `d3ro-voice-production` 최종 생성과 Android app
|
||||||
|
`com.d3ro.voice` 등록을 action-time 승인받는다.
|
||||||
|
3. 올바른 개인 Play Console 계정과 GitLab Maintainer 세션에 로그인한다.
|
||||||
|
4. Firebase fingerprints/config를 검증하고 GitLab protected/masked/hidden
|
||||||
|
variables를 구성한다.
|
||||||
|
5. protected exact `main` candidate job과 emulator prerequisite를 실행한다.
|
||||||
|
6. AAB/evidence를 독립 검증한 뒤 별도 승인으로 Play internal track에 올린다.
|
||||||
|
7. Play-signed arm64 설치본으로 핵심 E2E를 통과한다.
|
||||||
|
8. 실제 UI 기반 6장만 제작·검증·업로드한다.
|
||||||
|
9. `linux-builder`와 Forgejo 접근 경로를 복구한 뒤 run #46만 재실행한다.
|
||||||
|
|
||||||
|
Firebase 생성, GitLab variable 변경, Play 업로드, legal declaration, tag/public
|
||||||
|
release는 이번 감사에서 실행하지 않았다.
|
||||||
|
|
@ -190,6 +190,47 @@ function verifySourceContracts() {
|
||||||
assert(releaseWorkflow.includes('test "$GITHUB_SHA" = "$(git rev-parse origin/main)"'), 'dispatch_main_sha_guard_missing')
|
assert(releaseWorkflow.includes('test "$GITHUB_SHA" = "$(git rev-parse origin/main)"'), 'dispatch_main_sha_guard_missing')
|
||||||
assert(releaseWorkflow.includes('test "$RELEASE_TAG" = "v$VERSION_NAME"'), 'release_tag_identity_guard_missing')
|
assert(releaseWorkflow.includes('test "$RELEASE_TAG" = "v$VERSION_NAME"'), 'release_tag_identity_guard_missing')
|
||||||
|
|
||||||
|
const gitlabWorkflow = readWorkspaceFile('.gitlab-ci.yml')
|
||||||
|
const mobileProductionJobStart = gitlabWorkflow.indexOf('mobile-production-release:\n')
|
||||||
|
const publishReleaseJobStart = gitlabWorkflow.indexOf('\npublish-release:\n')
|
||||||
|
assert(mobileProductionJobStart >= 0, 'gitlab_mobile_production_job_missing')
|
||||||
|
assert(publishReleaseJobStart > mobileProductionJobStart, 'gitlab_publish_release_job_missing')
|
||||||
|
const mobileProductionJob = gitlabWorkflow.slice(mobileProductionJobStart, publishReleaseJobStart)
|
||||||
|
const publishReleaseJob = gitlabWorkflow.slice(publishReleaseJobStart)
|
||||||
|
for (const required of [
|
||||||
|
'git fetch --no-tags origin main:refs/remotes/origin/main',
|
||||||
|
'test "${CI_COMMIT_REF_PROTECTED:-}" = "true"',
|
||||||
|
'test -z "${CI_COMMIT_BRANCH:-}"',
|
||||||
|
'test "$CI_COMMIT_TAG" = "v${VERSION_NAME}"',
|
||||||
|
'test "$(git rev-parse "refs/tags/${CI_COMMIT_TAG}^{commit}")" = "$CI_COMMIT_SHA"',
|
||||||
|
'RELEASE_GIT_REF="refs/tags/${CI_COMMIT_TAG}"',
|
||||||
|
'test "${CI_COMMIT_BRANCH:-}" = "main"',
|
||||||
|
'test "$CI_COMMIT_SHA" = "$(git rev-parse origin/main)"',
|
||||||
|
'RELEASE_GIT_REF="refs/heads/main"',
|
||||||
|
'--git-ref "$RELEASE_GIT_REF"',
|
||||||
|
'--expected-git-ref "$RELEASE_GIT_REF"',
|
||||||
|
'$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG =~ /^v\\d+\\.\\d+\\.\\d+$/',
|
||||||
|
'$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH == "main"',
|
||||||
|
]) {
|
||||||
|
assert(
|
||||||
|
mobileProductionJob.includes(required),
|
||||||
|
`gitlab_mobile_candidate_contract_missing_${required.replace(/[^a-z0-9]+/gi, '_')}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assert(
|
||||||
|
(mobileProductionJob.match(/\n\s+when: manual/g) ?? []).length === 2,
|
||||||
|
'gitlab_mobile_candidate_manual_rules_invalid',
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
!mobileProductionJob.includes('--git-ref "refs/tags/$CI_COMMIT_TAG"'),
|
||||||
|
'gitlab_mobile_evidence_ref_tag_only',
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
publishReleaseJob.includes('$CI_COMMIT_TAG =~ /^v\\d+\\.\\d+\\.\\d+.*$/'),
|
||||||
|
'gitlab_public_release_stable_tag_rule_missing',
|
||||||
|
)
|
||||||
|
assert(!publishReleaseJob.includes('CI_COMMIT_BRANCH'), 'gitlab_public_release_branch_trigger_enabled')
|
||||||
|
|
||||||
const packageJson = JSON.parse(readWorkspaceFile('package.json'))
|
const packageJson = JSON.parse(readWorkspaceFile('package.json'))
|
||||||
assert(
|
assert(
|
||||||
packageJson.scripts?.['release:mobile:boundary'] === 'node scripts/ci/verify-mobile-release-boundary.mjs',
|
packageJson.scripts?.['release:mobile:boundary'] === 'node scripts/ci/verify-mobile-release-boundary.mjs',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue