From 5205dcdfa9db38e7ec962cf3b494ffc2b96b5226 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Sat, 29 Aug 2026 18:33:45 +0900 Subject: [PATCH] feat(release): prepare 1.1.0 candidate --- .dockerignore | 6 + .env.example | 6 + .eslintrc.cjs | 20 +- .forgejo/workflows/deploy-site-windows.yml | 7 +- .forgejo/workflows/deploy-site.yml | 21 +- .github/workflows/build-mac.yml | 3 - .github/workflows/ci.yml | 237 +- .github/workflows/deploy-site.yml | 3 + .github/workflows/release-signing-ca.yml | 3 - .github/workflows/release.yml | 400 +- .gitignore | 20 +- .gitlab-ci.yml | 334 +- .nvmrc | 2 +- CHANGELOG.md | 29 +- Dockerfile.admin | 6 +- apps/admin-swagger/openapi.json | 658 +- apps/admin/Dockerfile | 2 +- apps/admin/package.json | 2 +- apps/admin/src/app/(admin)/releases/page.tsx | 3 - apps/admin/src/app/api/admin/license/route.ts | 24 +- .../src/components/license-issuer-dialog.tsx | 8 - .../AdminAuthorizationE2ETests.cs | 119 + .../AdminOperationServiceTests.cs | 97 + .../AuthBootstrapControllerTests.cs | 91 + apps/api-server.Tests/AuthSecurityTests.cs | 118 + .../D3ROVoice.Api.Tests.csproj | 25 + .../SttControllerSecurityTests.cs | 191 + apps/api-server.Tests/SttFailClosedTests.cs | 223 + .../SttGatewayAuthorizationE2ETests.cs | 100 + apps/api-server/Controllers/AuthController.cs | 49 +- apps/api-server/Controllers/SttController.cs | 136 +- apps/api-server/D3ROVoice.Api.csproj | 12 +- apps/api-server/Data/AppDbContext.cs | 61 + apps/api-server/Dockerfile | 5 +- apps/api-server/Program.cs | 8 +- .../Services/AdminOperationService.cs | 102 + apps/api-server/Services/AuthService.cs | 109 +- apps/api-server/Services/SttProxyService.cs | 271 +- .../wwwroot/.well-known/assetlinks.json | 12 + apps/api-server/wwwroot/accept-invite.css | 345 + apps/api-server/wwwroot/accept-invite.html | 76 + apps/api-server/wwwroot/accept-invite.js | 37 + .../wwwroot/accept-invite/index.html | 76 + apps/api-server/wwwroot/download.html | 504 +- apps/api-server/wwwroot/index.html | 26 +- apps/desktop/electron-builder.yml | 4 +- apps/desktop/electron.vite.config.ts | 12 + apps/desktop/package.json | 4 +- .../resources/license/production-public.pem | 3 + apps/desktop/src/main/bootstrap.ts | 2 - apps/desktop/src/main/index.ts | 5 +- apps/desktop/src/main/ipc/audio-handlers.ts | 3 - apps/desktop/src/main/ipc/payment-handlers.ts | 264 +- apps/desktop/src/main/ipc/stt-handlers.ts | 2 +- apps/desktop/src/main/ipc/support-handlers.ts | 4 +- apps/desktop/src/main/ipc/voice-handlers.ts | 2 +- .../src/main/services/AudioCaptureService.ts | 1 - .../src/main/services/CaptionService.ts | 3 +- .../src/main/services/CloudSTTService.ts | 43 +- .../src/main/services/CloudSyncService.ts | 8 +- .../main/services/DictationTemplateService.ts | 2 - .../main/services/FileTranscriptionService.ts | 1 - .../src/main/services/LicenseService.ts | 36 +- .../src/main/services/MeetingModeService.ts | 12 +- apps/desktop/src/main/services/MemoService.ts | 2 +- .../src/main/services/OnlineLLMService.ts | 10 - .../src/main/services/PremiumLLMService.ts | 18 - apps/desktop/src/main/services/RAGService.ts | 1 - .../src/main/services/UpdateService.ts | 5 +- .../src/main/services/VoiceActionService.ts | 1 - .../src/main/services/VoiceModeService.ts | 1 - .../main/services/ads/AdMediationEngine.ts | 166 +- .../main/services/ads/AdSettlementService.ts | 125 +- .../src/main/services/ads/AppLovinAdapter.ts | 64 +- .../src/main/services/ads/CarbonAdsAdapter.ts | 62 +- .../services/ads/DirectHouseSponsorAdapter.ts | 119 +- .../main/services/ads/EthicalAdsAdapter.ts | 72 +- .../services/ads/GoogleAdManagerAdapter.ts | 65 +- .../src/main/services/ads/InMobiAdapter.ts | 59 +- .../src/main/services/ads/MintegralAdapter.ts | 59 +- .../src/main/services/ads/PlaywireAdapter.ts | 67 +- .../src/main/services/ads/PubMaticAdapter.ts | 53 +- .../main/services/ads/UnavailableAdAdapter.ts | 37 + .../src/main/services/ads/UnityAdsAdapter.ts | 59 +- .../src/main/services/stt/STTManager.ts | 1 - .../services/stt/drivers/D3ROCloudDriver.ts | 236 +- apps/desktop/src/main/update-feed.ts | 3 +- .../desktop/src/main/windows/WindowManager.ts | 8 +- apps/desktop/src/preload/index.ts | 9 +- .../src/renderer/components/AppLayout.tsx | 7 +- .../renderer/components/CloudSyncSection.tsx | 2 - .../components/CodexOAuthGuideModal.tsx | 42 +- .../renderer/components/HotkeyRecordModal.tsx | 30 +- .../src/renderer/components/LicenseTab.tsx | 8 +- .../renderer/components/OllamaGuideModal.tsx | 23 +- .../renderer/components/OnboardingModal.tsx | 29 +- .../src/renderer/components/STTTab.tsx | 16 +- .../src/renderer/components/SettingsModal.tsx | 42 +- .../renderer/components/TemplateSection.tsx | 2 +- .../src/renderer/components/TitleBar.tsx | 48 +- .../src/renderer/components/ads/AdBanner.tsx | 29 +- .../components/ads/RewardedQuotaModal.tsx | 106 +- .../components/meeting/MeetingDetailTabs.tsx | 10 +- .../components/payment/CheckoutModal.tsx | 421 +- .../components/payment/checkout-flow.ts | 203 + .../components/shared/EmptyStateCard.tsx | 2 +- .../components/support/SupportModal.tsx | 18 +- .../src/renderer/pages/CommandsPage.tsx | 11 +- .../src/renderer/pages/DashboardPage.tsx | 13 +- .../src/renderer/pages/DictionaryPage.tsx | 22 +- .../src/renderer/pages/HistoryPage.tsx | 8 +- .../src/renderer/pages/KnowledgeBasePage.tsx | 6 +- .../src/renderer/pages/MeetingModePage.tsx | 22 +- .../renderer/pages/VoiceConversationPage.tsx | 10 +- .../renderer/popups/command-popup/style.css | 4 +- .../renderer/popups/recording-tip/style.css | 2 +- apps/desktop/tests/e2e/dashboard.spec.ts | 2 - apps/desktop/tests/e2e/history.spec.ts | 1 - .../tests/e2e/launch_readiness.spec.ts | 4 +- apps/desktop/tests/e2e/navigation.spec.ts | 2 - apps/desktop/tests/e2e/pages_crud.spec.ts | 3 - apps/desktop/tests/e2e/settings.spec.ts | 2 - apps/desktop/tests/e2e/voice_pipeline.spec.ts | 3 - .../tests/main/services/STTManager.test.ts | 18 +- .../tests/red/ads/ad-settlement.e2e.test.ts | 136 + .../tests/red/ads/admob-ssv.e2e.test.ts | 258 + .../tests/red/ads/ads-mediation.e2e.test.ts | 370 + apps/desktop/tests/red/chain.usecase.test.ts | 2 +- .../red/cloud-stt-complex-journeys.test.ts | 208 +- .../tests/red/crypto-license.usecase.test.ts | 62 +- .../tests/red/instruction.usecase.test.ts | 1 - .../desktop/tests/red/meeting.usecase.test.ts | 2 +- apps/desktop/tests/red/rag.usecase.test.ts | 2 +- .../tests/red/silent-errors.usecase.test.ts | 1 - .../desktop/tests/red/stt-llm.usecase.test.ts | 41 +- .../tests/unit/AdMediationEngine.spec.ts | 296 +- apps/desktop/tests/unit/checkout-flow.spec.ts | 252 + .../tests/unit/payment-handlers.spec.ts | 332 + apps/desktop/vitest.config.ts | 2 +- apps/mobile-rn/.eslintignore | 5 + apps/mobile-rn/.eslintrc.js | 16 + apps/mobile-rn/.gitignore | 4 + apps/mobile-rn/.maestro/README.md | 37 + .../.maestro/admin-local-admin-complete.yaml | 59 + .../.maestro/admin-local-admin-open.yaml | 139 + .../.maestro/admin-local-manager-read.yaml | 35 + .../.maestro/admin-local-manager-update.yaml | 80 + .../.maestro/admin-local-manager.yaml | 63 + .../admin-local-ordinary-settings.yaml | 22 + .../.maestro/admin-local-ordinary.yaml | 59 + .../.maestro/admin-local-stale-check.yaml | 20 + .../.maestro/admin-local-stale-open.yaml | 90 + .../mobile-rn/.maestro/admin-local-super.yaml | 135 + .../.maestro/authenticated-parity.yaml | 251 + .../.maestro/fresh-install-auth.yaml | 103 + .../.maestro/fresh-install-signup.yaml | 155 + .../.maestro/incoming-share-local-check.yaml | 25 + .../.maestro/incoming-share-local-login.yaml | 56 + .../.maestro/invite-app-link-release.yaml | 53 + apps/mobile-rn/.maestro/invite-deep-link.yaml | 49 + apps/mobile-rn/App.tsx | 512 +- .../@react-native-documents-picker.js | 15 + .../react-native-google-mobile-ads.js | 44 + apps/mobile-rn/__mocks__/react-native-iap.js | 29 + .../__mocks__/react-native-keychain.js | 27 + .../__mocks__/react-native-nitro-sound.js | 20 + apps/mobile-rn/__mocks__/whisper-rn.js | 14 + apps/mobile-rn/__tests__/account-exit.test.ts | 84 + .../account-identities-screen.test.tsx | 152 + .../__tests__/account-local-data.test.ts | 97 + .../__tests__/action-service.test.ts | 349 + .../__tests__/admin-mobile.local.e2e.mjs | 452 ++ .../__tests__/admin-screens.test.tsx | 302 + .../mobile-rn/__tests__/admin-service.test.ts | 363 + .../__tests__/auth-privacy-boundary.test.tsx | 268 + .../__tests__/auth-privacy-ui.test.tsx | 43 + .../mobile-rn/__tests__/auth-redirect.test.ts | 120 + .../billing-restore-provider.test.tsx | 152 + apps/mobile-rn/__tests__/billing.test.ts | 324 + apps/mobile-rn/__tests__/chat-service.test.ts | 69 + .../__tests__/command-service.test.ts | 102 + .../__tests__/content-report-service.test.ts | 151 + .../__tests__/content-report-sheet.test.tsx | 96 + apps/mobile-rn/__tests__/dashboard.test.ts | 46 + .../data-portability-documents.test.ts | 125 + .../__tests__/data-portability-legacy.test.ts | 109 + .../__tests__/data-portability-schema.test.ts | 122 + .../data-portability-service.test.ts | 149 + .../data-portability.local.integration.mjs | 574 ++ apps/mobile-rn/__tests__/devices.test.ts | 36 + .../__tests__/dictionary-service.test.ts | 230 + .../__tests__/e2e-runtime-bootstrap.test.ts | 37 + apps/mobile-rn/__tests__/entitlement.test.ts | 71 + .../data-portability/desktop-dictionary.csv | 3 + .../data-portability/desktop-dictionary.json | 22 + .../data-portability/desktop-meeting.md | 32 + .../data-portability/foreign-owner.json | 9 + .../__tests__/fixtures/knowledge-e2e.md | 7 + .../__tests__/generation-idempotency.test.ts | 44 + apps/mobile-rn/__tests__/history.test.ts | 146 + .../__tests__/identity-linking.test.ts | 131 + .../__tests__/import-audio-validation.test.ts | 281 + .../__tests__/incoming-media-intent.test.ts | 169 + .../incoming-media-native-contract.test.ts | 52 + .../knowledge-actions.local.integration.mjs | 206 + .../__tests__/knowledge-service.test.ts | 243 + .../__tests__/login-oauth-screen.test.tsx | 134 + apps/mobile-rn/__tests__/logout.test.ts | 55 + .../meeting-creation-screen.test.tsx | 303 + .../meeting-recording-lifecycle.test.ts | 134 + apps/mobile-rn/__tests__/meetings.test.ts | 366 + apps/mobile-rn/__tests__/mobile-ads.test.tsx | 174 + .../__tests__/notification-native.test.ts | 71 + .../mobile-rn/__tests__/notifications.test.ts | 273 + .../mobile-rn/__tests__/oauth-sign-in.test.ts | 142 + .../__tests__/onboarding-navigation.test.ts | 23 + .../__tests__/pending-invite.test.ts | 72 + apps/mobile-rn/__tests__/pkce-s256.test.ts | 45 + apps/mobile-rn/__tests__/preferences.test.ts | 114 + .../__tests__/recording-durability.test.ts | 83 + .../__tests__/recording-language.test.ts | 15 + .../recording-lifecycle.local.integration.mjs | 267 + .../recording-logout-cleanup.test.ts | 78 + .../recording-native-contract.test.ts | 54 + .../__tests__/recording-queue-hook.test.tsx | 53 + .../__tests__/secure-auth-storage.test.ts | 156 + .../__tests__/signup-oauth-screen.test.tsx | 218 + .../__tests__/stt-quota.local.integration.mjs | 121 + .../__tests__/talk-llm-stream.test.ts | 173 + .../__tests__/talk-transcription.test.ts | 130 + apps/mobile-rn/__tests__/talk-tts.test.ts | 63 + .../__tests__/talk.local.integration.mjs | 135 + .../teams-push.local.integration.mjs | 320 + apps/mobile-rn/__tests__/teams.test.ts | 311 + .../templates-memos-screens.test.tsx | 237 + .../__tests__/templates-memos-service.test.ts | 269 + .../templates-memos.local.integration.mjs | 613 ++ .../__tests__/ui-native-theme.test.tsx | 133 + apps/mobile-rn/android/app/build.gradle | 441 +- apps/mobile-rn/android/app/proguard-rules.pro | 2 + .../app/src/androidTest/AndroidManifest.xml | 9 + .../com/d3ro/voice/CsprngInstrumentedTest.kt | 51 + .../voice/IncomingMediaInstrumentedTest.kt | 100 + .../d3ro/voice/IncomingMediaTestProvider.java | 97 + .../android/app/src/main/AndroidManifest.xml | 92 +- .../app/src/main/assets/models/README.md | 11 + .../assets/models/THIRD_PARTY_NOTICES.txt | 72 + .../com/d3ro/voice/D3ROAudioDecodeModule.kt | 556 ++ .../com/d3ro/voice/D3ROAudioDecodePackage.kt | 16 + .../java/com/d3ro/voice/D3ROConfigModule.kt | 63 + .../java/com/d3ro/voice/D3ROConfigPackage.kt | 16 + .../com/d3ro/voice/D3ROFileShareModule.kt | 211 + .../com/d3ro/voice/D3ROFileSharePackage.kt | 16 + .../voice/D3ROFirebaseMessagingService.kt | 38 + .../d3ro/voice/D3ROIncomingMediaIntents.kt | 608 ++ .../com/d3ro/voice/D3ROIncomingMediaModule.kt | 47 + .../d3ro/voice/D3ROIncomingMediaPackage.kt | 16 + .../com/d3ro/voice/D3RONotificationDeduper.kt | 65 + .../com/d3ro/voice/D3RONotificationDisplay.kt | 82 + .../com/d3ro/voice/D3RONotificationEvents.kt | 68 + .../com/d3ro/voice/D3RONotificationPayload.kt | 84 + .../com/d3ro/voice/D3RONotificationsModule.kt | 209 + .../d3ro/voice/D3RONotificationsPackage.kt | 16 + .../com/d3ro/voice/D3RORecordingJournal.kt | 175 + .../com/d3ro/voice/D3RORecordingModule.kt | 279 + .../com/d3ro/voice/D3RORecordingPackage.kt | 16 + .../com/d3ro/voice/D3RORecordingService.kt | 635 ++ .../java/com/d3ro/voice/D3ROTalkModule.kt | 393 + .../java/com/d3ro/voice/D3ROTalkPackage.kt | 16 + .../{d3rovoice => d3ro/voice}/MainActivity.kt | 17 +- .../voice}/MainApplication.kt | 11 +- .../res/drawable/d3ro_launcher_foreground.xml | 26 + .../res/drawable/d3ro_launcher_monochrome.xml | 20 + .../src/main/res/drawable/ic_stat_d3ro.xml | 9 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 4 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 4 + .../res/mipmap-anydpi-v33/ic_launcher.xml | 5 + .../mipmap-anydpi-v33/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3056 -> 3033 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5024 -> 3834 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2096 -> 1967 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2858 -> 2444 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4569 -> 4168 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7098 -> 5398 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6464 -> 6324 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10676 -> 8468 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9250 -> 8551 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15523 -> 11538 bytes .../android/app/src/main/res/raw/keep.xml | 1 + .../app/src/main/res/values-ko/strings.xml | 10 + .../app/src/main/res/values/colors.xml | 4 + .../app/src/main/res/values/strings.xml | 10 +- .../app/src/main/res/xml/d3ro_file_paths.xml | 6 + apps/mobile-rn/android/build.gradle | 22 + apps/mobile-rn/android/gradle.properties | 11 +- apps/mobile-rn/app.json | 9 +- apps/mobile-rn/index.js | 3 + .../ios/D3ROVoice.xcodeproj/project.pbxproj | 12 +- .../AppIcon.appiconset/AppIcon-ipad-20@1x.png | Bin 0 -> 320 bytes .../AppIcon.appiconset/AppIcon-ipad-20@2x.png | Bin 0 -> 636 bytes .../AppIcon.appiconset/AppIcon-ipad-29@1x.png | Bin 0 -> 467 bytes .../AppIcon.appiconset/AppIcon-ipad-29@2x.png | Bin 0 -> 925 bytes .../AppIcon.appiconset/AppIcon-ipad-40@1x.png | Bin 0 -> 636 bytes .../AppIcon.appiconset/AppIcon-ipad-40@2x.png | Bin 0 -> 1127 bytes .../AppIcon.appiconset/AppIcon-ipad-76@1x.png | Bin 0 -> 1212 bytes .../AppIcon.appiconset/AppIcon-ipad-76@2x.png | Bin 0 -> 2151 bytes .../AppIcon-ipad-83.5@2x.png | Bin 0 -> 2640 bytes .../AppIcon-iphone-20@2x.png | Bin 0 -> 636 bytes .../AppIcon-iphone-20@3x.png | Bin 0 -> 960 bytes .../AppIcon-iphone-29@2x.png | Bin 0 -> 925 bytes .../AppIcon-iphone-29@3x.png | Bin 0 -> 1377 bytes .../AppIcon-iphone-40@2x.png | Bin 0 -> 1127 bytes .../AppIcon-iphone-40@3x.png | Bin 0 -> 1745 bytes .../AppIcon-iphone-60@2x.png | Bin 0 -> 1745 bytes .../AppIcon-iphone-60@3x.png | Bin 0 -> 2837 bytes .../AppIcon-marketing-1024@1x.png | Bin 0 -> 20332 bytes .../AppIcon.appiconset/Contents.json | 125 +- apps/mobile-rn/ios/D3ROVoice/Info.plist | 12 + apps/mobile-rn/jest.config.js | 23 + .../android/en-US/changelogs/1010001.txt | 1 + .../android/ko-KR/changelogs/1010001.txt | 1 + apps/mobile-rn/package-lock.json | 3324 +++++++- apps/mobile-rn/package.json | 28 +- .../src/components/ContentReportSheet.tsx | 357 + .../src/components/FreeTierBanner.tsx | 98 + .../src/features/actions/action-service.ts | 673 ++ .../src/features/admin/admin-service.ts | 758 ++ .../src/features/admin/admin-session.ts | 15 + .../src/features/auth/identity-linking.ts | 160 + .../src/features/auth/oauth-sign-in.ts | 161 + .../src/features/chat/chat-service.ts | 173 + .../src/features/commands/command-service.ts | 433 ++ .../features/dashboard/dashboard-service.ts | 113 + .../account-portability-service.ts | 206 + .../data-portability/canonical-json.ts | 90 + .../data-portability/data-file-picker.ts | 190 + .../data-portability/document-binary.ts | 356 + .../src/features/data-portability/index.ts | 11 + .../data-portability/legacy-import.ts | 728 ++ .../data-portability/meeting-export.ts | 189 + .../data-portability/portability-error.ts | 81 + .../data-portability/portability-schema.ts | 972 +++ .../data-portability/portability-types.ts | 230 + .../data-portability/portable-file.ts | 260 + .../data-portability/portable-text.ts | 229 + .../src/features/devices/device-service.ts | 201 + .../features/dictionary/dictionary-service.ts | 428 ++ .../src/features/history/history-cache.ts | 174 + .../src/features/history/history-service.ts | 420 + .../src/features/history/use-history-sync.ts | 373 + .../features/import/android-audio-decoder.ts | 220 + .../src/features/import/audio-file-picker.ts | 128 + .../src/features/import/audio-import-types.ts | 83 + .../import/audio-transcription-service.ts | 921 +++ .../src/features/import/audio-validation.ts | 350 + .../features/import/incoming-media-intent.ts | 214 + .../import/local-whisper-transcription.ts | 146 + .../src/features/import/multipart-audio.ts | 78 + .../features/import/recorded-audio-input.ts | 24 + .../features/import/resumable-audio-upload.ts | 295 + .../src/features/import/whisper-rn-root.d.ts | 5 + .../knowledge/knowledge-file-picker.ts | 183 + .../features/knowledge/knowledge-service.ts | 638 ++ .../src/features/meetings/meetings-service.ts | 968 +++ apps/mobile-rn/src/features/memos/index.ts | 2 + .../src/features/memos/memo-service.ts | 264 + apps/mobile-rn/src/features/memos/types.ts | 19 + .../notifications/notification-contract.ts | 159 + .../notifications/notification-native.ts | 186 + .../notifications/notification-service.ts | 164 + .../notifications/use-notification-runtime.ts | 112 + .../recording/durable-processing-queue.ts | 519 ++ .../features/recording/recording-language.ts | 20 + .../use-recording-processing-queue.ts | 19 + .../reporting/content-report-service.ts | 190 + .../src/features/talk/llm-stream-service.ts | 304 + .../src/features/talk/talk-debug-audio.ts | 79 + .../talk/talk-transcription-service.ts | 240 + apps/mobile-rn/src/features/talk/talk-tts.ts | 104 + .../src/features/teams/pending-invite.ts | 48 + .../src/features/teams/team-service.ts | 694 ++ .../templates/generation-idempotency.ts | 50 + .../mobile-rn/src/features/templates/index.ts | 3 + .../features/templates/template-service.ts | 446 ++ .../mobile-rn/src/features/templates/types.ts | 43 + apps/mobile-rn/src/lib/account-exit.ts | 41 + apps/mobile-rn/src/lib/account-local-data.ts | 49 + apps/mobile-rn/src/lib/audio-recorder.ts | 677 ++ apps/mobile-rn/src/lib/auth-capabilities.ts | 44 + apps/mobile-rn/src/lib/auth-context.tsx | 317 +- apps/mobile-rn/src/lib/auth-redirect.ts | 177 + apps/mobile-rn/src/lib/billing-context.tsx | 583 ++ apps/mobile-rn/src/lib/device-context.tsx | 124 + .../src/lib/e2e-runtime-bootstrap.ts | 44 + .../mobile-rn/src/lib/entitlement-context.tsx | 419 + apps/mobile-rn/src/lib/logout.ts | 27 + apps/mobile-rn/src/lib/mobile-ads-context.tsx | 350 + apps/mobile-rn/src/lib/native-config.ts | 106 + apps/mobile-rn/src/lib/pkce-s256.ts | 55 + .../mobile-rn/src/lib/preferences-context.tsx | 909 +++ apps/mobile-rn/src/lib/random-id.ts | 20 + apps/mobile-rn/src/lib/secure-auth-storage.ts | 141 + apps/mobile-rn/src/lib/supabase.ts | 42 +- apps/mobile-rn/src/lib/update-manager.ts | 92 - .../mobile-rn/src/navigation/TabNavigator.tsx | 112 +- apps/mobile-rn/src/screens/AccountScreen.tsx | 1086 +++ apps/mobile-rn/src/screens/ActionsScreen.tsx | 569 ++ apps/mobile-rn/src/screens/AdminScreen.tsx | 491 ++ .../src/screens/AdminUserDetailScreen.tsx | 850 +++ apps/mobile-rn/src/screens/CommandsScreen.tsx | 610 ++ apps/mobile-rn/src/screens/DashScreen.tsx | 481 +- .../src/screens/DataPortabilityScreen.tsx | 328 + apps/mobile-rn/src/screens/DevicesScreen.tsx | 198 + .../src/screens/DictionaryScreen.tsx | 714 ++ .../src/screens/ForgotPasswordScreen.tsx | 245 + .../src/screens/HistoryDetailScreen.tsx | 698 ++ apps/mobile-rn/src/screens/HistoryScreen.tsx | 435 +- .../src/screens/InviteAcceptScreen.tsx | 222 + .../mobile-rn/src/screens/KnowledgeScreen.tsx | 720 ++ apps/mobile-rn/src/screens/LoginScreen.tsx | 375 +- .../src/screens/MeetingDetailScreen.tsx | 1172 +++ apps/mobile-rn/src/screens/MeetingsScreen.tsx | 803 ++ apps/mobile-rn/src/screens/MemosScreen.tsx | 573 ++ .../src/screens/NotificationsScreen.tsx | 299 + .../src/screens/OnboardingScreen.tsx | 578 ++ .../src/screens/ProPaywallScreen.tsx | 792 +- apps/mobile-rn/src/screens/RecordScreen.tsx | 1076 ++- apps/mobile-rn/src/screens/SettingsScreen.tsx | 892 ++- apps/mobile-rn/src/screens/SignUpScreen.tsx | 642 ++ apps/mobile-rn/src/screens/TalkScreen.tsx | 1081 ++- .../src/screens/TeamDetailScreen.tsx | 784 ++ apps/mobile-rn/src/screens/TeamsScreen.tsx | 322 + .../mobile-rn/src/screens/TemplatesScreen.tsx | 698 ++ .../src/screens/UpdatePasswordScreen.tsx | 277 + apps/mobile-rn/src/screens/WorksHubScreen.tsx | 515 ++ apps/mobile-rn/src/theme/mobile-theme.ts | 164 + .../mobile-rn/src/theme/themed-components.tsx | 170 + apps/web/e2e/billing-catalog.spec.ts | 74 + apps/web/e2e/billing.spec.ts | 219 + .../e2e/dashboard-dictionary-commands.spec.ts | 362 + apps/web/e2e/history.spec.ts | 172 + apps/web/e2e/payple-checkout.spec.ts | 236 + apps/web/e2e/web-stt-client.spec.ts | 84 + apps/web/env.example.txt | 4 + apps/web/package.json | 9 +- apps/web/public/.well-known/assetlinks.json | 12 + apps/web/public/download.html | 505 +- apps/web/src/app/(app)/billing/page.tsx | 445 +- apps/web/src/app/(app)/chat/page.tsx | 16 +- apps/web/src/app/(app)/commands/page.tsx | 496 ++ apps/web/src/app/(app)/dashboard/page.tsx | 413 +- apps/web/src/app/(app)/dictionary/page.tsx | 419 + apps/web/src/app/(app)/history/[id]/page.tsx | 340 + apps/web/src/app/(app)/history/page.tsx | 397 + apps/web/src/app/(app)/layout.tsx | 2 +- apps/web/src/app/(app)/record/page.tsx | 16 +- apps/web/src/app/accept-invite/page.tsx | 4 +- apps/web/src/app/download/page.tsx | 296 +- apps/web/src/app/layout.tsx | 9 + apps/web/src/app/login/page.tsx | 442 +- .../billing/billing-checkout-options.tsx | 62 + .../components/billing/checkout-button.tsx | 35 +- .../billing/payple-checkout-button.tsx | 232 +- .../src/components/billing/payple-client.ts | 324 + .../billing/payple-manage-button.tsx | 42 +- .../src/components/billing/portal-button.tsx | 26 +- apps/web/src/components/chat/chat-panel.tsx | 384 +- apps/web/src/components/layout/sidebar.tsx | 95 +- .../components/meetings/markdown-preview.tsx | 4 +- .../src/components/record/mic-recorder.tsx | 446 +- .../components/teams/invite-member-form.tsx | 2 +- apps/web/src/lib/billing-catalog.ts | 97 + apps/web/src/lib/command-client.ts | 417 + apps/web/src/lib/dashboard-client.ts | 228 + apps/web/src/lib/dictionary-client.ts | 267 + apps/web/src/lib/history-client.ts | 212 + apps/web/src/lib/web-stt-client.ts | 120 + design.md | 152 + docker-compose.nas.yml | 6 + docs/deployment/release-guide.md | 183 +- docs/monetization-plan.md | 72 + docs/v3/MOBILE_APP_COMPLETION_SSOT.md | 400 + ...OBILE_PRODUCTIZATION_HANDOFF_2026-08-21.md | 401 + docs/v3/e2e/RED_E2E_500_CONTENT_REPORT.md | 49 + docs/v3/play/01-store-listing.md | 126 + docs/v3/play/02-data-safety-draft.md | 121 + docs/v3/play/03-app-content-draft.md | 255 + docs/v3/play/04-release-checklist.md | 343 + .../play/05-content-reporting-operations.md | 184 + docs/v3/play/README.md | 62 + .../assets/d3ro-voice-legacy-icon-source.svg | 8 + .../play/assets/d3ro-voice-play-icon-512.png | Bin 0 -> 13455 bytes .../assets/d3ro-voice-play-icon-source.svg | 8 + .../assets/d3ro-voice-round-icon-source.svg | 8 + docs/v3/play/assets/feature-graphic/README.md | 57 + .../candidate-a-signal-grid.png | Bin 0 -> 25200 bytes .../candidate-a-signal-grid.svg | 17 + .../candidate-b-voice-orbit.png | Bin 0 -> 44972 bytes .../candidate-b-voice-orbit.svg | 16 + .../candidate-c-blueprint-band.png | Bin 0 -> 14337 bytes .../candidate-c-blueprint-band.svg | 27 + .../feature-graphic/candidate-d-monolith.png | Bin 0 -> 28737 bytes .../feature-graphic/candidate-d-monolith.svg | 15 + .../play/assets/feature-graphic/manifest.json | 57 + docs/v3/play/assets/mobile-icon-manifest.json | 270 + .../d3ro-icon-pixel-launcher-20260824.png | Bin 0 -> 297117 bytes global.json | 7 + package-lock.json | 6789 ++++++++++++----- package.json | 18 +- packages/api-client/__tests__/client.test.ts | 29 +- .../api-client/__tests__/transcribe.test.ts | 154 +- packages/api-client/package.json | 2 +- packages/api-client/src/client.ts | 44 +- packages/api-client/src/supabase-browser.ts | 14 +- packages/api-client/src/supabase-server.ts | 14 +- packages/api-client/src/transcribe.ts | 165 +- packages/api-client/src/types.ts | 329 + packages/core/package.json | 2 +- packages/core/src/supabase-config.ts | 18 + packages/core/src/types.ts | 31 +- packages/core/src/utils/crypto-license.ts | 104 +- packages/core/src/utils/meeting-markdown.ts | 4 +- packages/i18n/package.json | 2 +- packages/i18n/src/locales/de.json | 16 +- packages/i18n/src/locales/en.json | 1083 ++- packages/i18n/src/locales/es.json | 16 +- packages/i18n/src/locales/fr.json | 16 +- packages/i18n/src/locales/ja.json | 16 +- packages/i18n/src/locales/ko.json | 1094 ++- packages/i18n/src/locales/pt.json | 16 +- packages/i18n/src/locales/ru.json | 16 +- packages/i18n/src/locales/th.json | 16 +- packages/i18n/src/locales/vi.json | 16 +- packages/i18n/src/locales/zh-TW.json | 16 +- packages/i18n/src/locales/zh.json | 16 +- packages/ui-native/package.json | 2 +- .../ui-native/src/components/FilterChip.tsx | 16 +- packages/ui-native/src/components/Header.tsx | 11 +- packages/ui-native/src/components/Led.tsx | 22 +- .../ui-native/src/components/MetalCard.tsx | 10 +- .../ui-native/src/components/PhosphorText.tsx | 30 +- .../src/components/PhysicalButton.tsx | 26 +- .../ui-native/src/components/ScreenPanel.tsx | 6 +- .../ui-native/src/components/StatusBar.tsx | 10 +- .../ui-native/src/components/WaveBars.tsx | 70 +- packages/ui-native/src/index.ts | 6 + packages/ui-native/src/theme-context.tsx | 31 + packages/ui-native/src/theme.ts | 82 +- packages/ui/package.json | 2 +- .../ui/src/components/ds/InstrumentPanel.tsx | 2 +- .../ui/src/components/ds/SegmentControl.tsx | 2 +- packages/ui/src/theme.ts | 72 +- release/android-release-identity.json | 11 + release/mobile-release-evidence-public.pem | 3 + release/product-version.json | 8 + scripts/ci/audit-nas-stt-config.ps1 | 75 + scripts/ci/build-mobile-local-e2e.ps1 | 72 + scripts/ci/check-no-hardcoded-secrets.mjs | 139 + scripts/ci/create-desktop-license-keypair.mjs | 55 + scripts/ci/create-mobile-release-evidence.mjs | 145 + scripts/ci/create-release-evidence-key.mjs | 50 + scripts/ci/extract-release-notes.mjs | 40 + scripts/ci/mobile-release-evidence-lib.mjs | 737 ++ .../ci/prepare-mobile-release-publication.mjs | 75 + scripts/ci/prepare-whisper-model.mjs | 72 + scripts/ci/publish-gitlab-release.mjs | 199 +- scripts/ci/push-to-chanpaca-git.mjs | 76 +- .../ci/run-mobile-csprng-instrumentation.sh | 75 + scripts/ci/run-mobile-emulator-gate.sh | 129 + .../run-mobile-incoming-share-local-e2e.ps1 | 147 + .../ci/sync-and-publish-forgejo-release.mjs | 24 +- scripts/ci/sync-version.mjs | 292 +- .../ci/upload-asset-to-forgejo-release.mjs | 8 +- scripts/ci/verify-android-app-links.mjs | 295 + scripts/ci/verify-android-artifact.mjs | 1498 ++++ scripts/ci/verify-mobile-build-config.mjs | 408 + scripts/ci/verify-mobile-release-boundary.mjs | 818 ++ scripts/ci/verify-mobile-release-config.mjs | 403 + scripts/ci/verify-play-store-assets.mjs | 710 ++ .../ci/verify-release-evidence-key-pair.mjs | 50 + scripts/ci/verify-release-metadata.mjs | 245 + .../ci/verify-windows-android-upload-key.ps1 | 102 + scripts/deploy-nas.ps1 | 55 +- scripts/deploy-nas.sh | 41 +- scripts/deploy-site-to-nas.js | 88 + scripts/e2e-desktop-full-journey.js | 74 + scripts/gen-keystore.js | 54 + scripts/verify-e2e-downloads-and-releases.js | 13 +- server/supabase/config.toml | 48 +- .../supabase/functions/_shared/admin-auth.ts | 1 - .../functions/_shared/admin-contract.test.ts | 225 + .../functions/_shared/admin-contract.ts | 399 + .../functions/_shared/admin-current-role.ts | 22 + .../functions/_shared/admob-ssv.test.ts | 277 + .../supabase/functions/_shared/admob-ssv.ts | 281 + server/supabase/functions/_shared/auth.ts | 3 - .../functions/_shared/billing-catalog.test.ts | 59 + .../functions/_shared/billing-catalog.ts | 91 + .../_shared/content-report-contract.test.ts | 101 + .../_shared/content-report-contract.ts | 174 + server/supabase/functions/_shared/cors.ts | 5 +- .../_shared/generation-receipt.test.ts | 37 + .../functions/_shared/generation-receipt.ts | 44 + .../_shared/generative-ai-safety.test.ts | 29 + .../functions/_shared/generative-ai-safety.ts | 34 + .../functions/_shared/google-play.test.ts | 177 + .../supabase/functions/_shared/google-play.ts | 418 + .../functions/_shared/google-pubsub.test.ts | 88 + .../functions/_shared/google-pubsub.ts | 173 + .../functions/_shared/llm-contract.test.ts | 46 + .../functions/_shared/llm-contract.ts | 99 + .../_shared/meeting-document-contract.test.ts | 66 + .../_shared/meeting-document-contract.ts | 102 + .../supabase/functions/_shared/payple.test.ts | 179 + server/supabase/functions/_shared/payple.ts | 387 +- .../functions/_shared/push-contract.test.ts | 230 + .../functions/_shared/push-contract.ts | 540 ++ server/supabase/functions/_shared/quota.ts | 78 +- .../functions/_shared/stt-contract.test.ts | 112 + .../functions/_shared/stt-contract.ts | 165 + .../functions/_shared/team-contract.test.ts | 103 + .../functions/_shared/team-contract.ts | 199 + .../functions/account-delete/index.ts | 147 + .../functions/admin-audit-log/index.ts | 208 +- .../functions/admin-payments/index.ts | 148 +- .../functions/admin-subscriptions/index.ts | 357 +- .../supabase/functions/admin-users/index.ts | 254 +- server/supabase/functions/admob-ssv/index.ts | 68 + .../functions/billing-catalog/index.ts | 83 + .../functions/content-report/index.ts | 94 + server/supabase/functions/deno.json | 1 - server/supabase/functions/deno.lock | 72 + .../supabase/functions/embed-chunks/index.ts | 227 +- .../generate-meeting-document/index.ts | 240 + .../functions/google-play-rtdn/index.ts | 198 + server/supabase/functions/iap-verify/index.ts | 130 + server/supabase/functions/llm-proxy/index.ts | 197 +- .../functions/payple-checkout/index.ts | 231 +- .../supabase/functions/payple-manage/index.ts | 211 +- .../supabase/functions/payple-renew/index.ts | 361 +- .../functions/payple-webhook/index.test.ts | 53 + .../functions/payple-webhook/index.ts | 353 +- .../functions/realtime-token/index.ts | 8 +- .../functions/search-knowledge/index.ts | 151 +- server/supabase/functions/send-push/index.ts | 809 +- .../functions/stripe-checkout/index.ts | 253 +- .../supabase/functions/stripe-portal/index.ts | 45 +- .../functions/stripe-webhook/index.test.ts | 82 + .../functions/stripe-webhook/index.ts | 415 +- .../stt-proxy/index.contract.test.ts | 51 + server/supabase/functions/stt-proxy/index.ts | 259 +- .../supabase/functions/team-accept/index.ts | 197 +- .../supabase/functions/team-invite/index.ts | 379 +- ...60821000001_mobile_platform_foundation.sql | 616 ++ ...21000002_mobile_monetization_hardening.sql | 121 + ...1000003_payment_provider_serialization.sql | 1223 +++ ...0260821000004_mobile_runtime_integrity.sql | 206 + ...60821000005_synced_commands_dictionary.sql | 267 + .../20260821000006_atomic_command_reorder.sql | 69 + .../20260821000007_command_bootstrap_race.sql | 63 + .../20260821000008_atomic_team_security.sql | 611 ++ ...0821000009_team_push_dispatch_security.sql | 176 + ...821000010_team_function_acl_correction.sql | 163 + ...21000011_team_invite_replay_correction.sql | 87 + .../20260821000012_system_push_dispatch.sql | 83 + .../20260821000013_push_dispatch_limits.sql | 186 + ...60821000014_device_revocation_security.sql | 153 + .../20260821000015_durable_push_outbox.sql | 792 ++ ...60821000016_transactional_push_enqueue.sql | 166 + .../20260821000017_push_claim_rate_limit.sql | 181 + .../20260821000018_push_claim_lock_order.sql | 58 + ...821000019_push_claim_nonretryable_busy.sql | 57 + ..._transcription_outbox_completion_edges.sql | 50 + ...20260821000021_atomic_data_portability.sql | 1070 +++ ...60821000022_mobile_recording_lifecycle.sql | 396 + ...20260821000023_atomic_admin_management.sql | 308 + ...00024_mobile_templates_memos_documents.sql | 1333 ++++ ...260821000025_meeting_creation_metadata.sql | 357 + .../20260821000026_stt_quota_reservations.sql | 283 + ...027_meeting_creation_template_optional.sql | 111 + ...28_ad_reward_receipt_replay_protection.sql | 253 + .../20260824000029_content_reporting.sql | 564 ++ ...260824000030_profile_managed_field_acl.sql | 14 + .../20260825000031_audit_log_actor_unlink.sql | 18 + ...032_meeting_document_content_reporting.sql | 180 + .../supabase/tests/content-report-red.e2e.mjs | 724 ++ .../content-reporting.edge.integration.ps1 | 155 + .../tests/content-reporting.integration.sql | 533 ++ .../tests/mobile-platform.integration.ps1 | 652 ++ ...mobile-recording-lifecycle.integration.sql | 141 + .../tests/mobile-reward-race.integration.ps1 | 89 + .../mobile-runtime-integrity.integration.sql | 192 + .../tests/payment-provider.integration.sql | 453 ++ .../production_mobile_release_preflight.sql | 34 + .../public-mobile-runtime.integration.mjs | 79 + .../push-claim-lock-race.integration.sql | 116 + .../tests/push-outbox.integration.sql | 695 ++ .../stt-quota-reservations.integration.sql | 162 + .../tests/team-push-security.integration.sql | 438 ++ site/index.html | 8 +- site/package-lock.json | 4 +- site/package.json | 6 +- site/public/.well-known/assetlinks.json | 12 + site/public/accept-invite.css | 345 + site/public/accept-invite.html | 76 + site/public/accept-invite.js | 37 + site/public/accept-invite/index.html | 76 + site/public/delete-account/index.html | 44 + site/public/download.html | 504 +- site/public/favicon.svg | 10 +- site/public/legal.css | 111 + site/public/privacy/index.html | 58 + site/public/terms/index.html | 68 + site/src/components/Badge.tsx | 12 +- site/src/components/DataPoint.tsx | 6 +- site/src/components/GlowButton.tsx | 24 +- site/src/components/InstrumentCard.tsx | 9 +- site/src/components/LanguageSwitcher.tsx | 20 +- site/src/components/Led.tsx | 10 +- site/src/components/SectionHeader.tsx | 11 +- site/src/components/WaveBars.tsx | 2 +- site/src/hooks/useClientOS.ts | 58 +- site/src/index.css | 122 +- site/src/sections/CTA.tsx | 46 +- site/src/sections/Download.tsx | 415 +- site/src/sections/FAQ.tsx | 59 +- site/src/sections/Features.tsx | 82 +- site/src/sections/Footer.tsx | 34 +- site/src/sections/Header.tsx | 53 +- site/src/sections/Hero.tsx | 216 +- site/src/sections/HowItWorks.tsx | 111 +- site/src/sections/Pricing.tsx | 190 +- site/src/sections/Privacy.tsx | 58 +- site/src/tokens.ts | 67 +- site/tailwind.config.js | 123 +- site/vite.config.ts | 35 +- 736 files changed, 115667 insertions(+), 12203 deletions(-) create mode 100644 apps/api-server.Tests/AdminAuthorizationE2ETests.cs create mode 100644 apps/api-server.Tests/AdminOperationServiceTests.cs create mode 100644 apps/api-server.Tests/AuthBootstrapControllerTests.cs create mode 100644 apps/api-server.Tests/AuthSecurityTests.cs create mode 100644 apps/api-server.Tests/D3ROVoice.Api.Tests.csproj create mode 100644 apps/api-server.Tests/SttControllerSecurityTests.cs create mode 100644 apps/api-server.Tests/SttFailClosedTests.cs create mode 100644 apps/api-server.Tests/SttGatewayAuthorizationE2ETests.cs create mode 100644 apps/api-server/Services/AdminOperationService.cs create mode 100644 apps/api-server/wwwroot/.well-known/assetlinks.json create mode 100644 apps/api-server/wwwroot/accept-invite.css create mode 100644 apps/api-server/wwwroot/accept-invite.html create mode 100644 apps/api-server/wwwroot/accept-invite.js create mode 100644 apps/api-server/wwwroot/accept-invite/index.html create mode 100644 apps/desktop/resources/license/production-public.pem create mode 100644 apps/desktop/src/main/services/ads/UnavailableAdAdapter.ts create mode 100644 apps/desktop/src/renderer/components/payment/checkout-flow.ts create mode 100644 apps/desktop/tests/red/ads/ad-settlement.e2e.test.ts create mode 100644 apps/desktop/tests/red/ads/admob-ssv.e2e.test.ts create mode 100644 apps/desktop/tests/red/ads/ads-mediation.e2e.test.ts create mode 100644 apps/desktop/tests/unit/checkout-flow.spec.ts create mode 100644 apps/desktop/tests/unit/payment-handlers.spec.ts create mode 100644 apps/mobile-rn/.eslintignore create mode 100644 apps/mobile-rn/.maestro/README.md create mode 100644 apps/mobile-rn/.maestro/admin-local-admin-complete.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-admin-open.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-manager-read.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-manager-update.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-manager.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-ordinary-settings.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-ordinary.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-stale-check.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-stale-open.yaml create mode 100644 apps/mobile-rn/.maestro/admin-local-super.yaml create mode 100644 apps/mobile-rn/.maestro/authenticated-parity.yaml create mode 100644 apps/mobile-rn/.maestro/fresh-install-auth.yaml create mode 100644 apps/mobile-rn/.maestro/fresh-install-signup.yaml create mode 100644 apps/mobile-rn/.maestro/incoming-share-local-check.yaml create mode 100644 apps/mobile-rn/.maestro/incoming-share-local-login.yaml create mode 100644 apps/mobile-rn/.maestro/invite-app-link-release.yaml create mode 100644 apps/mobile-rn/.maestro/invite-deep-link.yaml create mode 100644 apps/mobile-rn/__mocks__/@react-native-documents-picker.js create mode 100644 apps/mobile-rn/__mocks__/react-native-google-mobile-ads.js create mode 100644 apps/mobile-rn/__mocks__/react-native-iap.js create mode 100644 apps/mobile-rn/__mocks__/react-native-keychain.js create mode 100644 apps/mobile-rn/__mocks__/react-native-nitro-sound.js create mode 100644 apps/mobile-rn/__mocks__/whisper-rn.js create mode 100644 apps/mobile-rn/__tests__/account-exit.test.ts create mode 100644 apps/mobile-rn/__tests__/account-identities-screen.test.tsx create mode 100644 apps/mobile-rn/__tests__/account-local-data.test.ts create mode 100644 apps/mobile-rn/__tests__/action-service.test.ts create mode 100644 apps/mobile-rn/__tests__/admin-mobile.local.e2e.mjs create mode 100644 apps/mobile-rn/__tests__/admin-screens.test.tsx create mode 100644 apps/mobile-rn/__tests__/admin-service.test.ts create mode 100644 apps/mobile-rn/__tests__/auth-privacy-boundary.test.tsx create mode 100644 apps/mobile-rn/__tests__/auth-privacy-ui.test.tsx create mode 100644 apps/mobile-rn/__tests__/auth-redirect.test.ts create mode 100644 apps/mobile-rn/__tests__/billing-restore-provider.test.tsx create mode 100644 apps/mobile-rn/__tests__/billing.test.ts create mode 100644 apps/mobile-rn/__tests__/chat-service.test.ts create mode 100644 apps/mobile-rn/__tests__/command-service.test.ts create mode 100644 apps/mobile-rn/__tests__/content-report-service.test.ts create mode 100644 apps/mobile-rn/__tests__/content-report-sheet.test.tsx create mode 100644 apps/mobile-rn/__tests__/dashboard.test.ts create mode 100644 apps/mobile-rn/__tests__/data-portability-documents.test.ts create mode 100644 apps/mobile-rn/__tests__/data-portability-legacy.test.ts create mode 100644 apps/mobile-rn/__tests__/data-portability-schema.test.ts create mode 100644 apps/mobile-rn/__tests__/data-portability-service.test.ts create mode 100644 apps/mobile-rn/__tests__/data-portability.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/devices.test.ts create mode 100644 apps/mobile-rn/__tests__/dictionary-service.test.ts create mode 100644 apps/mobile-rn/__tests__/e2e-runtime-bootstrap.test.ts create mode 100644 apps/mobile-rn/__tests__/entitlement.test.ts create mode 100644 apps/mobile-rn/__tests__/fixtures/data-portability/desktop-dictionary.csv create mode 100644 apps/mobile-rn/__tests__/fixtures/data-portability/desktop-dictionary.json create mode 100644 apps/mobile-rn/__tests__/fixtures/data-portability/desktop-meeting.md create mode 100644 apps/mobile-rn/__tests__/fixtures/data-portability/foreign-owner.json create mode 100644 apps/mobile-rn/__tests__/fixtures/knowledge-e2e.md create mode 100644 apps/mobile-rn/__tests__/generation-idempotency.test.ts create mode 100644 apps/mobile-rn/__tests__/history.test.ts create mode 100644 apps/mobile-rn/__tests__/identity-linking.test.ts create mode 100644 apps/mobile-rn/__tests__/import-audio-validation.test.ts create mode 100644 apps/mobile-rn/__tests__/incoming-media-intent.test.ts create mode 100644 apps/mobile-rn/__tests__/incoming-media-native-contract.test.ts create mode 100644 apps/mobile-rn/__tests__/knowledge-actions.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/knowledge-service.test.ts create mode 100644 apps/mobile-rn/__tests__/login-oauth-screen.test.tsx create mode 100644 apps/mobile-rn/__tests__/logout.test.ts create mode 100644 apps/mobile-rn/__tests__/meeting-creation-screen.test.tsx create mode 100644 apps/mobile-rn/__tests__/meeting-recording-lifecycle.test.ts create mode 100644 apps/mobile-rn/__tests__/meetings.test.ts create mode 100644 apps/mobile-rn/__tests__/mobile-ads.test.tsx create mode 100644 apps/mobile-rn/__tests__/notification-native.test.ts create mode 100644 apps/mobile-rn/__tests__/notifications.test.ts create mode 100644 apps/mobile-rn/__tests__/oauth-sign-in.test.ts create mode 100644 apps/mobile-rn/__tests__/onboarding-navigation.test.ts create mode 100644 apps/mobile-rn/__tests__/pending-invite.test.ts create mode 100644 apps/mobile-rn/__tests__/pkce-s256.test.ts create mode 100644 apps/mobile-rn/__tests__/preferences.test.ts create mode 100644 apps/mobile-rn/__tests__/recording-durability.test.ts create mode 100644 apps/mobile-rn/__tests__/recording-language.test.ts create mode 100644 apps/mobile-rn/__tests__/recording-lifecycle.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/recording-logout-cleanup.test.ts create mode 100644 apps/mobile-rn/__tests__/recording-native-contract.test.ts create mode 100644 apps/mobile-rn/__tests__/recording-queue-hook.test.tsx create mode 100644 apps/mobile-rn/__tests__/secure-auth-storage.test.ts create mode 100644 apps/mobile-rn/__tests__/signup-oauth-screen.test.tsx create mode 100644 apps/mobile-rn/__tests__/stt-quota.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/talk-llm-stream.test.ts create mode 100644 apps/mobile-rn/__tests__/talk-transcription.test.ts create mode 100644 apps/mobile-rn/__tests__/talk-tts.test.ts create mode 100644 apps/mobile-rn/__tests__/talk.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/teams-push.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/teams.test.ts create mode 100644 apps/mobile-rn/__tests__/templates-memos-screens.test.tsx create mode 100644 apps/mobile-rn/__tests__/templates-memos-service.test.ts create mode 100644 apps/mobile-rn/__tests__/templates-memos.local.integration.mjs create mode 100644 apps/mobile-rn/__tests__/ui-native-theme.test.tsx create mode 100644 apps/mobile-rn/android/app/src/androidTest/AndroidManifest.xml create mode 100644 apps/mobile-rn/android/app/src/androidTest/java/com/d3ro/voice/CsprngInstrumentedTest.kt create mode 100644 apps/mobile-rn/android/app/src/androidTest/java/com/d3ro/voice/IncomingMediaInstrumentedTest.kt create mode 100644 apps/mobile-rn/android/app/src/androidTest/java/com/d3ro/voice/IncomingMediaTestProvider.java create mode 100644 apps/mobile-rn/android/app/src/main/assets/models/README.md create mode 100644 apps/mobile-rn/android/app/src/main/assets/models/THIRD_PARTY_NOTICES.txt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROAudioDecodeModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROAudioDecodePackage.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROConfigModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROConfigPackage.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROFileShareModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROFileSharePackage.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROFirebaseMessagingService.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROIncomingMediaIntents.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROIncomingMediaModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROIncomingMediaPackage.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RONotificationDeduper.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RONotificationDisplay.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RONotificationEvents.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RONotificationPayload.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RONotificationsModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RONotificationsPackage.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingJournal.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingPackage.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3RORecordingService.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROTalkModule.kt create mode 100644 apps/mobile-rn/android/app/src/main/java/com/d3ro/voice/D3ROTalkPackage.kt rename apps/mobile-rn/android/app/src/main/java/com/{d3rovoice => d3ro/voice}/MainActivity.kt (58%) rename apps/mobile-rn/android/app/src/main/java/com/{d3rovoice => d3ro/voice}/MainApplication.kt (68%) create mode 100644 apps/mobile-rn/android/app/src/main/res/drawable/d3ro_launcher_foreground.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/drawable/d3ro_launcher_monochrome.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/drawable/ic_stat_d3ro.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/mipmap-anydpi-v33/ic_launcher_round.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/raw/keep.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/values-ko/strings.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/values/colors.xml create mode 100644 apps/mobile-rn/android/app/src/main/res/xml/d3ro_file_paths.xml create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-20@1x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-20@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-29@1x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-29@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-40@1x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-40@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-76@1x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-76@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-ipad-83.5@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-20@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-20@3x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-29@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-29@3x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-40@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-40@3x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-60@2x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-iphone-60@3x.png create mode 100644 apps/mobile-rn/ios/D3ROVoice/Images.xcassets/AppIcon.appiconset/AppIcon-marketing-1024@1x.png create mode 100644 apps/mobile-rn/metadata/android/en-US/changelogs/1010001.txt create mode 100644 apps/mobile-rn/metadata/android/ko-KR/changelogs/1010001.txt create mode 100644 apps/mobile-rn/src/components/ContentReportSheet.tsx create mode 100644 apps/mobile-rn/src/components/FreeTierBanner.tsx create mode 100644 apps/mobile-rn/src/features/actions/action-service.ts create mode 100644 apps/mobile-rn/src/features/admin/admin-service.ts create mode 100644 apps/mobile-rn/src/features/admin/admin-session.ts create mode 100644 apps/mobile-rn/src/features/auth/identity-linking.ts create mode 100644 apps/mobile-rn/src/features/auth/oauth-sign-in.ts create mode 100644 apps/mobile-rn/src/features/chat/chat-service.ts create mode 100644 apps/mobile-rn/src/features/commands/command-service.ts create mode 100644 apps/mobile-rn/src/features/dashboard/dashboard-service.ts create mode 100644 apps/mobile-rn/src/features/data-portability/account-portability-service.ts create mode 100644 apps/mobile-rn/src/features/data-portability/canonical-json.ts create mode 100644 apps/mobile-rn/src/features/data-portability/data-file-picker.ts create mode 100644 apps/mobile-rn/src/features/data-portability/document-binary.ts create mode 100644 apps/mobile-rn/src/features/data-portability/index.ts create mode 100644 apps/mobile-rn/src/features/data-portability/legacy-import.ts create mode 100644 apps/mobile-rn/src/features/data-portability/meeting-export.ts create mode 100644 apps/mobile-rn/src/features/data-portability/portability-error.ts create mode 100644 apps/mobile-rn/src/features/data-portability/portability-schema.ts create mode 100644 apps/mobile-rn/src/features/data-portability/portability-types.ts create mode 100644 apps/mobile-rn/src/features/data-portability/portable-file.ts create mode 100644 apps/mobile-rn/src/features/data-portability/portable-text.ts create mode 100644 apps/mobile-rn/src/features/devices/device-service.ts create mode 100644 apps/mobile-rn/src/features/dictionary/dictionary-service.ts create mode 100644 apps/mobile-rn/src/features/history/history-cache.ts create mode 100644 apps/mobile-rn/src/features/history/history-service.ts create mode 100644 apps/mobile-rn/src/features/history/use-history-sync.ts create mode 100644 apps/mobile-rn/src/features/import/android-audio-decoder.ts create mode 100644 apps/mobile-rn/src/features/import/audio-file-picker.ts create mode 100644 apps/mobile-rn/src/features/import/audio-import-types.ts create mode 100644 apps/mobile-rn/src/features/import/audio-transcription-service.ts create mode 100644 apps/mobile-rn/src/features/import/audio-validation.ts create mode 100644 apps/mobile-rn/src/features/import/incoming-media-intent.ts create mode 100644 apps/mobile-rn/src/features/import/local-whisper-transcription.ts create mode 100644 apps/mobile-rn/src/features/import/multipart-audio.ts create mode 100644 apps/mobile-rn/src/features/import/recorded-audio-input.ts create mode 100644 apps/mobile-rn/src/features/import/resumable-audio-upload.ts create mode 100644 apps/mobile-rn/src/features/import/whisper-rn-root.d.ts create mode 100644 apps/mobile-rn/src/features/knowledge/knowledge-file-picker.ts create mode 100644 apps/mobile-rn/src/features/knowledge/knowledge-service.ts create mode 100644 apps/mobile-rn/src/features/meetings/meetings-service.ts create mode 100644 apps/mobile-rn/src/features/memos/index.ts create mode 100644 apps/mobile-rn/src/features/memos/memo-service.ts create mode 100644 apps/mobile-rn/src/features/memos/types.ts create mode 100644 apps/mobile-rn/src/features/notifications/notification-contract.ts create mode 100644 apps/mobile-rn/src/features/notifications/notification-native.ts create mode 100644 apps/mobile-rn/src/features/notifications/notification-service.ts create mode 100644 apps/mobile-rn/src/features/notifications/use-notification-runtime.ts create mode 100644 apps/mobile-rn/src/features/recording/durable-processing-queue.ts create mode 100644 apps/mobile-rn/src/features/recording/recording-language.ts create mode 100644 apps/mobile-rn/src/features/recording/use-recording-processing-queue.ts create mode 100644 apps/mobile-rn/src/features/reporting/content-report-service.ts create mode 100644 apps/mobile-rn/src/features/talk/llm-stream-service.ts create mode 100644 apps/mobile-rn/src/features/talk/talk-debug-audio.ts create mode 100644 apps/mobile-rn/src/features/talk/talk-transcription-service.ts create mode 100644 apps/mobile-rn/src/features/talk/talk-tts.ts create mode 100644 apps/mobile-rn/src/features/teams/pending-invite.ts create mode 100644 apps/mobile-rn/src/features/teams/team-service.ts create mode 100644 apps/mobile-rn/src/features/templates/generation-idempotency.ts create mode 100644 apps/mobile-rn/src/features/templates/index.ts create mode 100644 apps/mobile-rn/src/features/templates/template-service.ts create mode 100644 apps/mobile-rn/src/features/templates/types.ts create mode 100644 apps/mobile-rn/src/lib/account-exit.ts create mode 100644 apps/mobile-rn/src/lib/account-local-data.ts create mode 100644 apps/mobile-rn/src/lib/audio-recorder.ts create mode 100644 apps/mobile-rn/src/lib/auth-capabilities.ts create mode 100644 apps/mobile-rn/src/lib/auth-redirect.ts create mode 100644 apps/mobile-rn/src/lib/billing-context.tsx create mode 100644 apps/mobile-rn/src/lib/device-context.tsx create mode 100644 apps/mobile-rn/src/lib/e2e-runtime-bootstrap.ts create mode 100644 apps/mobile-rn/src/lib/entitlement-context.tsx create mode 100644 apps/mobile-rn/src/lib/logout.ts create mode 100644 apps/mobile-rn/src/lib/mobile-ads-context.tsx create mode 100644 apps/mobile-rn/src/lib/native-config.ts create mode 100644 apps/mobile-rn/src/lib/pkce-s256.ts create mode 100644 apps/mobile-rn/src/lib/preferences-context.tsx create mode 100644 apps/mobile-rn/src/lib/random-id.ts create mode 100644 apps/mobile-rn/src/lib/secure-auth-storage.ts delete mode 100644 apps/mobile-rn/src/lib/update-manager.ts create mode 100644 apps/mobile-rn/src/screens/AccountScreen.tsx create mode 100644 apps/mobile-rn/src/screens/ActionsScreen.tsx create mode 100644 apps/mobile-rn/src/screens/AdminScreen.tsx create mode 100644 apps/mobile-rn/src/screens/AdminUserDetailScreen.tsx create mode 100644 apps/mobile-rn/src/screens/CommandsScreen.tsx create mode 100644 apps/mobile-rn/src/screens/DataPortabilityScreen.tsx create mode 100644 apps/mobile-rn/src/screens/DevicesScreen.tsx create mode 100644 apps/mobile-rn/src/screens/DictionaryScreen.tsx create mode 100644 apps/mobile-rn/src/screens/ForgotPasswordScreen.tsx create mode 100644 apps/mobile-rn/src/screens/HistoryDetailScreen.tsx create mode 100644 apps/mobile-rn/src/screens/InviteAcceptScreen.tsx create mode 100644 apps/mobile-rn/src/screens/KnowledgeScreen.tsx create mode 100644 apps/mobile-rn/src/screens/MeetingDetailScreen.tsx create mode 100644 apps/mobile-rn/src/screens/MeetingsScreen.tsx create mode 100644 apps/mobile-rn/src/screens/MemosScreen.tsx create mode 100644 apps/mobile-rn/src/screens/NotificationsScreen.tsx create mode 100644 apps/mobile-rn/src/screens/OnboardingScreen.tsx create mode 100644 apps/mobile-rn/src/screens/SignUpScreen.tsx create mode 100644 apps/mobile-rn/src/screens/TeamDetailScreen.tsx create mode 100644 apps/mobile-rn/src/screens/TeamsScreen.tsx create mode 100644 apps/mobile-rn/src/screens/TemplatesScreen.tsx create mode 100644 apps/mobile-rn/src/screens/UpdatePasswordScreen.tsx create mode 100644 apps/mobile-rn/src/screens/WorksHubScreen.tsx create mode 100644 apps/mobile-rn/src/theme/mobile-theme.ts create mode 100644 apps/mobile-rn/src/theme/themed-components.tsx create mode 100644 apps/web/e2e/billing-catalog.spec.ts create mode 100644 apps/web/e2e/billing.spec.ts create mode 100644 apps/web/e2e/dashboard-dictionary-commands.spec.ts create mode 100644 apps/web/e2e/history.spec.ts create mode 100644 apps/web/e2e/payple-checkout.spec.ts create mode 100644 apps/web/e2e/web-stt-client.spec.ts create mode 100644 apps/web/public/.well-known/assetlinks.json create mode 100644 apps/web/src/app/(app)/commands/page.tsx create mode 100644 apps/web/src/app/(app)/dictionary/page.tsx create mode 100644 apps/web/src/app/(app)/history/[id]/page.tsx create mode 100644 apps/web/src/app/(app)/history/page.tsx create mode 100644 apps/web/src/components/billing/billing-checkout-options.tsx create mode 100644 apps/web/src/components/billing/payple-client.ts create mode 100644 apps/web/src/lib/billing-catalog.ts create mode 100644 apps/web/src/lib/command-client.ts create mode 100644 apps/web/src/lib/dashboard-client.ts create mode 100644 apps/web/src/lib/dictionary-client.ts create mode 100644 apps/web/src/lib/history-client.ts create mode 100644 apps/web/src/lib/web-stt-client.ts create mode 100644 design.md create mode 100644 docs/monetization-plan.md create mode 100644 docs/v3/MOBILE_APP_COMPLETION_SSOT.md create mode 100644 docs/v3/MOBILE_PRODUCTIZATION_HANDOFF_2026-08-21.md create mode 100644 docs/v3/e2e/RED_E2E_500_CONTENT_REPORT.md create mode 100644 docs/v3/play/01-store-listing.md create mode 100644 docs/v3/play/02-data-safety-draft.md create mode 100644 docs/v3/play/03-app-content-draft.md create mode 100644 docs/v3/play/04-release-checklist.md create mode 100644 docs/v3/play/05-content-reporting-operations.md create mode 100644 docs/v3/play/README.md create mode 100644 docs/v3/play/assets/d3ro-voice-legacy-icon-source.svg create mode 100644 docs/v3/play/assets/d3ro-voice-play-icon-512.png create mode 100644 docs/v3/play/assets/d3ro-voice-play-icon-source.svg create mode 100644 docs/v3/play/assets/d3ro-voice-round-icon-source.svg create mode 100644 docs/v3/play/assets/feature-graphic/README.md create mode 100644 docs/v3/play/assets/feature-graphic/candidate-a-signal-grid.png create mode 100644 docs/v3/play/assets/feature-graphic/candidate-a-signal-grid.svg create mode 100644 docs/v3/play/assets/feature-graphic/candidate-b-voice-orbit.png create mode 100644 docs/v3/play/assets/feature-graphic/candidate-b-voice-orbit.svg create mode 100644 docs/v3/play/assets/feature-graphic/candidate-c-blueprint-band.png create mode 100644 docs/v3/play/assets/feature-graphic/candidate-c-blueprint-band.svg create mode 100644 docs/v3/play/assets/feature-graphic/candidate-d-monolith.png create mode 100644 docs/v3/play/assets/feature-graphic/candidate-d-monolith.svg create mode 100644 docs/v3/play/assets/feature-graphic/manifest.json create mode 100644 docs/v3/play/assets/mobile-icon-manifest.json create mode 100644 docs/v3/play/assets/proof/d3ro-icon-pixel-launcher-20260824.png create mode 100644 global.json create mode 100644 packages/ui-native/src/theme-context.tsx create mode 100644 release/android-release-identity.json create mode 100644 release/mobile-release-evidence-public.pem create mode 100644 release/product-version.json create mode 100644 scripts/ci/audit-nas-stt-config.ps1 create mode 100644 scripts/ci/build-mobile-local-e2e.ps1 create mode 100644 scripts/ci/check-no-hardcoded-secrets.mjs create mode 100644 scripts/ci/create-desktop-license-keypair.mjs create mode 100644 scripts/ci/create-mobile-release-evidence.mjs create mode 100644 scripts/ci/create-release-evidence-key.mjs create mode 100644 scripts/ci/extract-release-notes.mjs create mode 100644 scripts/ci/mobile-release-evidence-lib.mjs create mode 100644 scripts/ci/prepare-mobile-release-publication.mjs create mode 100644 scripts/ci/prepare-whisper-model.mjs create mode 100644 scripts/ci/run-mobile-csprng-instrumentation.sh create mode 100644 scripts/ci/run-mobile-emulator-gate.sh create mode 100644 scripts/ci/run-mobile-incoming-share-local-e2e.ps1 create mode 100644 scripts/ci/verify-android-app-links.mjs create mode 100644 scripts/ci/verify-android-artifact.mjs create mode 100644 scripts/ci/verify-mobile-build-config.mjs create mode 100644 scripts/ci/verify-mobile-release-boundary.mjs create mode 100644 scripts/ci/verify-mobile-release-config.mjs create mode 100644 scripts/ci/verify-play-store-assets.mjs create mode 100644 scripts/ci/verify-release-evidence-key-pair.mjs create mode 100644 scripts/ci/verify-release-metadata.mjs create mode 100644 scripts/ci/verify-windows-android-upload-key.ps1 create mode 100644 scripts/deploy-site-to-nas.js create mode 100644 scripts/e2e-desktop-full-journey.js create mode 100644 scripts/gen-keystore.js create mode 100644 server/supabase/functions/_shared/admin-contract.test.ts create mode 100644 server/supabase/functions/_shared/admin-contract.ts create mode 100644 server/supabase/functions/_shared/admin-current-role.ts create mode 100644 server/supabase/functions/_shared/admob-ssv.test.ts create mode 100644 server/supabase/functions/_shared/admob-ssv.ts create mode 100644 server/supabase/functions/_shared/billing-catalog.test.ts create mode 100644 server/supabase/functions/_shared/billing-catalog.ts create mode 100644 server/supabase/functions/_shared/content-report-contract.test.ts create mode 100644 server/supabase/functions/_shared/content-report-contract.ts create mode 100644 server/supabase/functions/_shared/generation-receipt.test.ts create mode 100644 server/supabase/functions/_shared/generation-receipt.ts create mode 100644 server/supabase/functions/_shared/generative-ai-safety.test.ts create mode 100644 server/supabase/functions/_shared/generative-ai-safety.ts create mode 100644 server/supabase/functions/_shared/google-play.test.ts create mode 100644 server/supabase/functions/_shared/google-play.ts create mode 100644 server/supabase/functions/_shared/google-pubsub.test.ts create mode 100644 server/supabase/functions/_shared/google-pubsub.ts create mode 100644 server/supabase/functions/_shared/llm-contract.test.ts create mode 100644 server/supabase/functions/_shared/llm-contract.ts create mode 100644 server/supabase/functions/_shared/meeting-document-contract.test.ts create mode 100644 server/supabase/functions/_shared/meeting-document-contract.ts create mode 100644 server/supabase/functions/_shared/payple.test.ts create mode 100644 server/supabase/functions/_shared/push-contract.test.ts create mode 100644 server/supabase/functions/_shared/push-contract.ts create mode 100644 server/supabase/functions/_shared/stt-contract.test.ts create mode 100644 server/supabase/functions/_shared/stt-contract.ts create mode 100644 server/supabase/functions/_shared/team-contract.test.ts create mode 100644 server/supabase/functions/_shared/team-contract.ts create mode 100644 server/supabase/functions/account-delete/index.ts create mode 100644 server/supabase/functions/admob-ssv/index.ts create mode 100644 server/supabase/functions/billing-catalog/index.ts create mode 100644 server/supabase/functions/content-report/index.ts create mode 100644 server/supabase/functions/deno.lock create mode 100644 server/supabase/functions/generate-meeting-document/index.ts create mode 100644 server/supabase/functions/google-play-rtdn/index.ts create mode 100644 server/supabase/functions/iap-verify/index.ts create mode 100644 server/supabase/functions/payple-webhook/index.test.ts create mode 100644 server/supabase/functions/stripe-webhook/index.test.ts create mode 100644 server/supabase/functions/stt-proxy/index.contract.test.ts create mode 100644 server/supabase/migrations/20260821000001_mobile_platform_foundation.sql create mode 100644 server/supabase/migrations/20260821000002_mobile_monetization_hardening.sql create mode 100644 server/supabase/migrations/20260821000003_payment_provider_serialization.sql create mode 100644 server/supabase/migrations/20260821000004_mobile_runtime_integrity.sql create mode 100644 server/supabase/migrations/20260821000005_synced_commands_dictionary.sql create mode 100644 server/supabase/migrations/20260821000006_atomic_command_reorder.sql create mode 100644 server/supabase/migrations/20260821000007_command_bootstrap_race.sql create mode 100644 server/supabase/migrations/20260821000008_atomic_team_security.sql create mode 100644 server/supabase/migrations/20260821000009_team_push_dispatch_security.sql create mode 100644 server/supabase/migrations/20260821000010_team_function_acl_correction.sql create mode 100644 server/supabase/migrations/20260821000011_team_invite_replay_correction.sql create mode 100644 server/supabase/migrations/20260821000012_system_push_dispatch.sql create mode 100644 server/supabase/migrations/20260821000013_push_dispatch_limits.sql create mode 100644 server/supabase/migrations/20260821000014_device_revocation_security.sql create mode 100644 server/supabase/migrations/20260821000015_durable_push_outbox.sql create mode 100644 server/supabase/migrations/20260821000016_transactional_push_enqueue.sql create mode 100644 server/supabase/migrations/20260821000017_push_claim_rate_limit.sql create mode 100644 server/supabase/migrations/20260821000018_push_claim_lock_order.sql create mode 100644 server/supabase/migrations/20260821000019_push_claim_nonretryable_busy.sql create mode 100644 server/supabase/migrations/20260821000020_transcription_outbox_completion_edges.sql create mode 100644 server/supabase/migrations/20260821000021_atomic_data_portability.sql create mode 100644 server/supabase/migrations/20260821000022_mobile_recording_lifecycle.sql create mode 100644 server/supabase/migrations/20260821000023_atomic_admin_management.sql create mode 100644 server/supabase/migrations/20260821000024_mobile_templates_memos_documents.sql create mode 100644 server/supabase/migrations/20260821000025_meeting_creation_metadata.sql create mode 100644 server/supabase/migrations/20260821000026_stt_quota_reservations.sql create mode 100644 server/supabase/migrations/20260821000027_meeting_creation_template_optional.sql create mode 100644 server/supabase/migrations/20260821000028_ad_reward_receipt_replay_protection.sql create mode 100644 server/supabase/migrations/20260824000029_content_reporting.sql create mode 100644 server/supabase/migrations/20260824000030_profile_managed_field_acl.sql create mode 100644 server/supabase/migrations/20260825000031_audit_log_actor_unlink.sql create mode 100644 server/supabase/migrations/20260829000032_meeting_document_content_reporting.sql create mode 100644 server/supabase/tests/content-report-red.e2e.mjs create mode 100644 server/supabase/tests/content-reporting.edge.integration.ps1 create mode 100644 server/supabase/tests/content-reporting.integration.sql create mode 100644 server/supabase/tests/mobile-platform.integration.ps1 create mode 100644 server/supabase/tests/mobile-recording-lifecycle.integration.sql create mode 100644 server/supabase/tests/mobile-reward-race.integration.ps1 create mode 100644 server/supabase/tests/mobile-runtime-integrity.integration.sql create mode 100644 server/supabase/tests/payment-provider.integration.sql create mode 100644 server/supabase/tests/production_mobile_release_preflight.sql create mode 100644 server/supabase/tests/public-mobile-runtime.integration.mjs create mode 100644 server/supabase/tests/push-claim-lock-race.integration.sql create mode 100644 server/supabase/tests/push-outbox.integration.sql create mode 100644 server/supabase/tests/stt-quota-reservations.integration.sql create mode 100644 server/supabase/tests/team-push-security.integration.sql create mode 100644 site/public/.well-known/assetlinks.json create mode 100644 site/public/accept-invite.css create mode 100644 site/public/accept-invite.html create mode 100644 site/public/accept-invite.js create mode 100644 site/public/accept-invite/index.html create mode 100644 site/public/delete-account/index.html create mode 100644 site/public/legal.css create mode 100644 site/public/privacy/index.html create mode 100644 site/public/terms/index.html diff --git a/.dockerignore b/.dockerignore index 589bedb..181db27 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,9 +8,15 @@ node_modules/ dist/ **/dist/ build/ +**/build/ out/ +**/out/ test-results/ +**/test-results/ +**/playwright-report/ coverage/ +**/coverage/ +**/*.tsbuildinfo *.log *.tar bin/ diff --git a/.env.example b/.env.example index 53c17a4..9c310cc 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,12 @@ ADMIN_COOKIE_SECURE=true # Use a distinct random value with at least 32 UTF-8 bytes. D3RO_API_TOKEN= +# Offline Ed25519 license keys. Keep the private key on the admin server only. +# The matching public key is committed at apps/desktop/resources/license/production-public.pem. +# Multiline private PEM values may use escaped \n characters. Legacy fixture keys are development-only. +ADMIN_LICENSE_PRIVATE_KEY= +D3RO_ALLOW_LEGACY_DEV_LICENSES=false + # Supabase SUPABASE_URL= SUPABASE_SERVICE_ROLE_KEY= diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bc2269b..fef7baf 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -16,10 +16,28 @@ module.exports = { sourceType: 'module' }, plugins: ['@typescript-eslint'], + ignorePatterns: ['**/next-env.d.ts', '**/.next/**', '**/out/**', '**/dist/**'], rules: { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 'no-console': 'error' - } + }, + overrides: [ + { + files: ['apps/desktop/src/main/**/*.ts'], + rules: { + '@typescript-eslint/no-require-imports': 'off' + } + }, + { + files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', 'apps/desktop/tests/**/*.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-require-imports': 'off', + 'no-console': 'off', + 'require-yield': 'off' + } + } + ] } diff --git a/.forgejo/workflows/deploy-site-windows.yml b/.forgejo/workflows/deploy-site-windows.yml index 5d8171c..4d56a91 100644 --- a/.forgejo/workflows/deploy-site-windows.yml +++ b/.forgejo/workflows/deploy-site-windows.yml @@ -2,8 +2,6 @@ name: deploy-site-windows on: workflow_dispatch: - push: - branches: [main] jobs: deploy-win: @@ -21,11 +19,10 @@ jobs: git checkout -q -f FETCH_HEAD git clean -qfdx - - name: 사이트 빌드 & 바이너리 동기화 + - name: 사이트 빌드 run: | - npm install --prefix site + npm ci --prefix site npm run build --prefix site - node scripts/ci/sync-and-publish-forgejo-release.mjs - name: Cloudflare Pages 배포 env: diff --git a/.forgejo/workflows/deploy-site.yml b/.forgejo/workflows/deploy-site.yml index 7c58a14..c6b0767 100644 --- a/.forgejo/workflows/deploy-site.yml +++ b/.forgejo/workflows/deploy-site.yml @@ -3,9 +3,6 @@ name: deploy-site on: push: branches: [main] - tags: ["v*"] - release: - types: [published] workflow_dispatch: jobs: @@ -26,9 +23,9 @@ jobs: - name: 의존성 설치 및 사이트 빌드 run: | - npm ci --prefix site || npm install --prefix site + npm ci --prefix site npm run build --prefix site - node scripts/ci/sync-and-publish-forgejo-release.mjs || true + node -e "const fs=require('node:fs'); const v=require('./release/product-version.json').version; fs.writeFileSync('site/dist/release-identity.json', JSON.stringify({commit:process.env.GITHUB_SHA,version:v},null,2)+'\n')" - name: Cloudflare Pages 배포 (d3ro.chanpaca.net) env: @@ -46,5 +43,17 @@ jobs: --branch main \ --commit-dirty=true else - echo "CLOUDFLARE_API_TOKEN 없음" + echo "CLOUDFLARE_API_TOKEN 없음" >&2 + exit 1 fi + + - name: 공개 배포 식별자 검증 + run: | + curl --fail --silent --show-error --retry 6 --retry-delay 5 \ + https://d3ro.chanpaca.net/release-identity.json \ + --output release-identity.live.json + node -e "const fs=require('node:fs'); const live=JSON.parse(fs.readFileSync('release-identity.live.json','utf8')); if(live.commit!==process.env.GITHUB_SHA) throw new Error('public commit mismatch'); const expected=require('./release/product-version.json').version; if(live.version!==expected) throw new Error('public version mismatch')" + curl --fail --silent --show-error https://d3ro.chanpaca.net/ --output /dev/null + curl --fail --silent --show-error https://d3ro.chanpaca.net/privacy/ --output /dev/null + curl --fail --silent --show-error https://d3ro.chanpaca.net/terms/ --output /dev/null + curl --fail --silent --show-error https://d3ro.chanpaca.net/delete-account/ --output /dev/null diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml index e8beb46..3c9ea50 100644 --- a/.github/workflows/build-mac.yml +++ b/.github/workflows/build-mac.yml @@ -1,9 +1,6 @@ name: Build macOS on: - push: - tags: - - 'v*' workflow_dispatch: inputs: notarize: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da8f376..65d8b16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,22 +30,80 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + - name: Setup Node.js 24 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'npm' - name: Install Dependencies run: npm ci + - name: Credential Scanner Self-Test + run: npm run security:secrets:test + + - name: Hard-Coded Credential Scan + run: npm run security:secrets + + - name: Mobile Release Boundary Self-Test + run: npm run release:mobile:boundary:test + + - name: Mobile Release Configuration Self-Test + run: npm run release:mobile:config:test + + - name: Mobile Build Configuration Self-Test + run: npm run release:mobile:build-config:test + + - name: Play Store Asset Contract + run: npm run release:play:assets + - name: Lint Check run: npm run lint - continue-on-error: true - name: Typecheck All Workspaces run: npm run typecheck + api-server-tests: + name: .NET API Server Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.302' + + - name: Restore API Test Dependencies + run: dotnet restore apps/api-server.Tests/D3ROVoice.Api.Tests.csproj + + - name: Run API Authorization and Gateway Tests + run: dotnet test apps/api-server.Tests/D3ROVoice.Api.Tests.csproj --configuration Release --no-restore -p:StaticWebAssetsEnabled=false + + edge-functions-quality: + name: Supabase Edge Functions Typecheck & Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Deno 2.8.1 + uses: denoland/setup-deno@v2 + with: + deno-version: v2.8.1 + + - name: Check Every Edge Function Entrypoint + shell: bash + run: | + set -euo pipefail + for entrypoint in server/supabase/functions/*/index.ts; do + deno check --config server/supabase/functions/deno.json "$entrypoint" + done + + - name: Run Edge Function Contract Tests + run: deno test --config server/supabase/functions/deno.json --allow-read --allow-env server/supabase/functions + # ────────────────────────────────────────────────────────────────── # 2. Automated Test Matrix (Windows / macOS / Ubuntu) # ────────────────────────────────────────────────────────────────── @@ -61,10 +119,10 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + - name: Setup Node.js 24 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'npm' - name: Install Dependencies @@ -93,10 +151,10 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + - name: Setup Node.js 24 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'npm' - name: Install Dependencies @@ -104,3 +162,162 @@ jobs: - name: Build Target Workspace run: ${{ matrix.cmd }} + + # ────────────────────────────────────────────────────────────────── + # 4. Android x86_64 artifacts and native dependency gate + # ────────────────────────────────────────────────────────────────── + mobile-android: + name: Mobile Android (universal debug + bundled universal E2E) + needs: code-quality + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Node.js 24 + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Setup JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Setup Android SDK + uses: android-actions/setup-android@v4 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 + with: + cache-provider: basic + + - name: Install Dependencies + run: | + npm ci + npm --prefix apps/mobile-rn ci --workspaces=false + + - name: Prepare Verified Whisper Model + run: node scripts/ci/prepare-whisper-model.mjs + + - name: Test Mobile TypeScript and Jest + run: | + npm --prefix apps/mobile-rn run lint + npm --prefix apps/mobile-rn run typecheck + npm --prefix apps/mobile-rn run test:ci + + - name: Build Universal Debug, CSPRNG Test, and Bundled Universal E2E APKs + working-directory: apps/mobile-rn/android + env: + D3RO_VERSION_NAME: 0.0.0-e2e.${{ github.run_number }} + D3RO_VERSION_CODE: ${{ github.run_number }} + run: ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest :app:assembleE2e -PreactNativeArchitectures=arm64-v8a,x86_64 --no-daemon + + - name: Verify BuildConfig and APK Runtime Contracts + env: + D3RO_VERSION_NAME: 0.0.0-e2e.${{ github.run_number }} + D3RO_VERSION_CODE: ${{ github.run_number }} + run: | + set -euo pipefail + DEBUG_APK=apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk + E2E_APK=apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk + test -f "$DEBUG_APK" + test -f "$E2E_APK" + node scripts/ci/verify-mobile-build-config.mjs debug \ + | tee apps/mobile-rn/android/app/build/outputs/debug-build-config.json + node scripts/ci/verify-mobile-build-config.mjs e2e \ + | tee apps/mobile-rn/android/app/build/outputs/e2e-build-config.json + node scripts/ci/verify-android-artifact.mjs --mode debug --apk "$DEBUG_APK" \ + | tee apps/mobile-rn/android/app/build/outputs/debug-artifact-evidence.json + node scripts/ci/verify-android-artifact.mjs \ + --mode e2e \ + --apk "$E2E_APK" \ + --expected-version-name "$D3RO_VERSION_NAME" \ + --expected-version-code "$D3RO_VERSION_CODE" \ + | tee apps/mobile-rn/android/app/build/outputs/e2e-artifact-evidence.json + node scripts/ci/verify-android-app-links.mjs \ + | tee apps/mobile-rn/android/app/build/outputs/app-links-evidence.json + sha256sum "$DEBUG_APK" "$E2E_APK" | tee apps/mobile-rn/android/app/build/outputs/android-ci.sha256 + + - name: Upload Universal Android Artifacts + uses: actions/upload-artifact@v4 + with: + name: d3ro-mobile-android-universal-e2e + path: | + apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk + apps/mobile-rn/android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk + apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk + apps/mobile-rn/android/app/build/outputs/android-ci.sha256 + apps/mobile-rn/android/app/build/outputs/*-build-config.json + apps/mobile-rn/android/app/build/outputs/*-artifact-evidence.json + apps/mobile-rn/android/app/build/outputs/app-links-evidence.json + if-no-files-found: error + + # ────────────────────────────────────────────────────────────────── + # 5. Installed bundled APK on a clean API 35 x86_64 emulator + # ────────────────────────────────────────────────────────────────── + mobile-emulator-e2e: + name: Mobile Emulator E2E (API 35) + needs: mobile-android + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Setup Android SDK + uses: android-actions/setup-android@v4 + + - name: Download Bundled Android Artifact + uses: actions/download-artifact@v4 + with: + name: d3ro-mobile-android-universal-e2e + path: mobile-artifact + + - name: Install Verified Maestro CLI 2.7.0 + run: | + curl -fsSL https://github.com/mobile-dev-inc/maestro/releases/download/cli-2.7.0/maestro.zip -o /tmp/maestro.zip + echo 'a4ccab6b604617e7aef6db4f885666056eabe5cfa32befaa3bc994041b8fcbb5 /tmp/maestro.zip' | sha256sum -c - + unzip -q /tmp/maestro.zip -d "$RUNNER_TEMP/maestro" + echo "$RUNNER_TEMP/maestro/maestro/bin" >> "$GITHUB_PATH" + + - name: Run Mandatory Clean-room and Optional External-account Journeys + uses: reactivecircus/android-emulator-runner@v2 + env: + MOBILE_E2E_EMAIL: ${{ secrets.MOBILE_E2E_EMAIL }} + MOBILE_E2E_PASSWORD: ${{ secrets.MOBILE_E2E_PASSWORD }} + with: + api-level: 35 + target: google_apis + arch: x86_64 + profile: pixel_6 + disable-animations: true + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + script: | + set -euo pipefail + DEBUG_APK="$(find mobile-artifact -path '*/apk/debug/app-debug.apk' -print -quit)" + TEST_APK="$(find mobile-artifact -name app-debug-androidTest.apk -print -quit)" + E2E_APK="$(find mobile-artifact -name app-e2e.apk -print -quit)" + test -n "$DEBUG_APK" + test -n "$TEST_APK" + test -n "$E2E_APK" + maestro --version + bash scripts/ci/run-mobile-csprng-instrumentation.sh "$DEBUG_APK" "$TEST_APK" + bash scripts/ci/run-mobile-emulator-gate.sh "$E2E_APK" + + - name: Upload Emulator Evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: d3ro-mobile-emulator-e2e + path: | + apps/mobile-rn/.maestro/*.junit.xml + apps/mobile-rn/.maestro-output/ + if-no-files-found: warn diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 252deb5..74afdcb 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -31,6 +31,9 @@ jobs: working-directory: site run: npm ci + - name: Verify Mobile Release Publication Boundary + run: node scripts/ci/verify-mobile-release-boundary.mjs --self-test + - name: Build working-directory: site run: npm run build diff --git a/.github/workflows/release-signing-ca.yml b/.github/workflows/release-signing-ca.yml index 1af45ef..2eb432c 100644 --- a/.github/workflows/release-signing-ca.yml +++ b/.github/workflows/release-signing-ca.yml @@ -1,9 +1,6 @@ name: Release & Code Signing CA Pipeline on: - push: - tags: - - 'v*' workflow_dispatch: jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93b0dc4..6e670a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,27 +12,92 @@ on: version: description: 'Release version (e.g. 1.0.0)' required: true - default: '1.0.0' + default: '1.1.0' permissions: contents: write packages: write jobs: + release-preflight: + name: Release Preflight + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js 24 + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.302' + + - name: Setup Deno 2.8.1 + uses: denoland/setup-deno@v2 + with: + deno-version: v2.8.1 + + - name: Install JavaScript Dependencies + run: | + npm ci + npm --prefix apps/mobile-rn ci --workspaces=false + + - name: Verify Source, Security, Tests, and Play Assets + run: | + npm run version:check + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + node scripts/ci/sync-version.mjs --check --tag "$GITHUB_REF_NAME" + fi + npm run release:metadata:test + npm run security:secrets:test + npm run security:secrets + npm run release:mobile:boundary:test + npm run release:mobile:config:test + npm run release:mobile:build-config:test + npm run release:play:assets + npm run lint + npm run typecheck + npm test + npm --prefix apps/mobile-rn run lint + npm --prefix apps/mobile-rn run typecheck + npm --prefix apps/mobile-rn run test:ci + + - name: Check and Test Every Supabase Edge Function + shell: bash + run: | + set -euo pipefail + for entrypoint in server/supabase/functions/*/index.ts; do + deno check --config server/supabase/functions/deno.json "$entrypoint" + done + deno test --config server/supabase/functions/deno.json --allow-read --allow-env server/supabase/functions + + - name: Test .NET API Authorization and Gateway Boundaries + run: | + dotnet restore apps/api-server.Tests/D3ROVoice.Api.Tests.csproj + dotnet test apps/api-server.Tests/D3ROVoice.Api.Tests.csproj --configuration Release --no-restore -p:StaticWebAssetsEnabled=false + # ────────────────────────────────────────────────────────────────── # 1. Package Windows Installer (.exe & .blockmap & latest.yml) # ────────────────────────────────────────────────────────────────── package-windows: name: Package Windows Desktop App + needs: release-preflight runs-on: windows-latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + - name: Setup Node.js 24 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'npm' - name: Install Dependencies @@ -40,6 +105,7 @@ jobs: - name: Build All Workspaces run: | + npm run version:check npm run typecheck npm run build --workspace=@d3ro/desktop @@ -66,15 +132,16 @@ jobs: # ────────────────────────────────────────────────────────────────── package-macos: name: Package macOS Desktop App + needs: release-preflight runs-on: macos-latest steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + - name: Setup Node.js 24 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'npm' - name: Install Dependencies @@ -82,6 +149,7 @@ jobs: - name: Build All Workspaces run: | + npm run version:check npm run typecheck npm run build --workspace=@d3ro/desktop @@ -108,10 +176,267 @@ jobs: apps/desktop/release/*/latest-mac.yml # ────────────────────────────────────────────────────────────────── - # 3. Build & Containerize Admin Dashboard + # 3. Package signed Android APK/AAB (arm64, production-only config) + # ────────────────────────────────────────────────────────────────── + package-android: + name: Package Android Mobile App + needs: release-preflight + runs-on: ubuntu-latest + environment: mobile-production-release + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Establish Trusted Mobile Release Identity + id: android-version + shell: bash + env: + DISPATCH_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + git fetch --no-tags origin main + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + test "$GITHUB_REF" = "refs/heads/main" + test "$GITHUB_SHA" = "$(git rev-parse origin/main)" + VERSION_NAME="$DISPATCH_VERSION" + RELEASE_TAG="v$VERSION_NAME" + else + [[ "$GITHUB_REF" == refs/tags/v* ]] + VERSION_NAME="${GITHUB_REF_NAME#v}" + RELEASE_TAG="$GITHUB_REF_NAME" + git merge-base --is-ancestor "$GITHUB_SHA" origin/main + fi + SSOT_VERSION="$(node -p "require('./release/product-version.json').version")" + VERSION_CODE="$(node -p "require('./release/product-version.json').androidVersionCode")" + [[ "$VERSION_NAME" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] + test "$VERSION_NAME" = "$SSOT_VERSION" + test "$RELEASE_TAG" = "v$VERSION_NAME" + [[ "$VERSION_CODE" =~ ^[1-9][0-9]{0,9}$ ]] + test "$VERSION_CODE" -le 2100000000 + printf 'name=%s\n' "$VERSION_NAME" >> "$GITHUB_OUTPUT" + printf 'code=%s\n' "$VERSION_CODE" >> "$GITHUB_OUTPUT" + printf 'tag=%s\n' "$RELEASE_TAG" >> "$GITHUB_OUTPUT" + + - name: Verify Immutable Checkout Identity + shell: bash + run: | + set -euo pipefail + test "$GITHUB_SHA" = "$(git rev-parse HEAD)" + test -z "$(git status --porcelain --untracked-files=all)" + git rev-parse "${GITHUB_SHA}^{tree}" + + - name: Setup Node.js 24 + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Verify Mobile Release Boundary Source Contract + run: node scripts/ci/verify-mobile-release-boundary.mjs --self-test + + - name: Verify Mobile Release Configuration Contract + run: npm run release:mobile:config:test + + - name: Verify Mobile Build Configuration Contract + run: npm run release:mobile:build-config:test + + - name: Verify Play Store Asset Contract + run: npm run release:play:assets + + - name: Require Restricted AAB Handoff Visibility + shell: bash + env: + REPOSITORY_VISIBILITY: ${{ github.event.repository.visibility }} + run: | + set -euo pipefail + test "$REPOSITORY_VISIBILITY" = "private" + + - name: Setup JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Setup Android SDK + uses: android-actions/setup-android@v4 + + - name: Install Pinned Official Bundletool + shell: bash + run: | + set -euo pipefail + curl --fail --silent --show-error --location \ + --output "$RUNNER_TEMP/bundletool-all-1.18.3.jar" \ + https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar + printf '%s %s\n' \ + a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29 \ + "$RUNNER_TEMP/bundletool-all-1.18.3.jar" | sha256sum --check --strict + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 + with: + cache-provider: basic + + - name: Install Dependencies + run: | + npm ci + npm --prefix apps/mobile-rn ci --workspaces=false + + - name: Materialize Release-only Configuration + shell: bash + env: + ANDROID_RELEASE_KEYSTORE_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }} + ANDROID_GOOGLE_SERVICES_JSON_B64: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON_B64 }} + run: | + set -euo pipefail + umask 077 + test -n "$ANDROID_RELEASE_KEYSTORE_B64" + test -n "$ANDROID_GOOGLE_SERVICES_JSON_B64" + printf '%s' "$ANDROID_RELEASE_KEYSTORE_B64" | base64 --decode > apps/mobile-rn/android/app/release.keystore + printf '%s' "$ANDROID_GOOGLE_SERVICES_JSON_B64" | base64 --decode > apps/mobile-rn/android/app/google-services.json + test -s apps/mobile-rn/android/app/release.keystore + test -s apps/mobile-rn/android/app/google-services.json + + - name: Prepare Verified Whisper Model + run: node scripts/ci/prepare-whisper-model.mjs + + - name: Verify Production Firebase and AdMob Configuration + env: + D3RO_FIREBASE_EXPECTED_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }} + D3RO_FIREBASE_EXPECTED_PROJECT_NUMBER: ${{ secrets.FIREBASE_PROJECT_NUMBER }} + D3RO_FIREBASE_EXPECTED_MOBILESDK_APP_ID: ${{ secrets.FIREBASE_MOBILESDK_APP_ID }} + run: | + export D3RO_ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")" + export D3RO_ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")" + export D3RO_ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")" + npm run release:mobile:config + + - name: Test Mobile TypeScript and Jest + run: | + npm --prefix apps/mobile-rn run lint + npm --prefix apps/mobile-rn run typecheck + npm --prefix apps/mobile-rn run test:ci + + - name: Build Signed arm64 APK and AAB + working-directory: apps/mobile-rn/android + env: + D3RO_RELEASE_STORE_FILE: ${{ github.workspace }}/apps/mobile-rn/android/app/release.keystore + D3RO_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_STORE_PASSWORD }} + D3RO_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }} + D3RO_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }} + D3RO_VERSION_NAME: ${{ steps.android-version.outputs.name }} + D3RO_VERSION_CODE: ${{ steps.android-version.outputs.code }} + run: | + export D3RO_ADMOB_APP_ID="$(node -p "require('../../../release/android-release-identity.json').adMobAppId")" + export D3RO_ADMOB_BANNER_UNIT_ID="$(node -p "require('../../../release/android-release-identity.json').adMobBannerUnitId")" + export D3RO_ADMOB_REWARDED_UNIT_ID="$(node -p "require('../../../release/android-release-identity.json').adMobRewardedUnitId")" + ./gradlew :app:assembleRelease :app:bundleRelease -PreactNativeArchitectures=arm64-v8a --no-daemon + + - name: Materialize Release Evidence Signing Key + shell: bash + env: + ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64: ${{ secrets.ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64 }} + run: | + set -euo pipefail + umask 077 + test -n "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64" + printf '%s' "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64" | base64 --decode > apps/mobile-rn/android/app/release-evidence-private.pem + test -s apps/mobile-rn/android/app/release-evidence-private.pem + + - name: Verify Release BuildConfig, Signature, ABI, Bundle, Ads, and Offline Model + shell: bash + env: + D3RO_VERSION_NAME: ${{ steps.android-version.outputs.name }} + D3RO_VERSION_CODE: ${{ steps.android-version.outputs.code }} + run: | + set -euo pipefail + D3RO_ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")" + D3RO_ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")" + D3RO_ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")" + ANDROID_UPLOAD_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').uploadCertificateSha256")" + PLAY_APP_SIGNING_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').playAppSigningCertificateSha256")" + APK=apps/mobile-rn/android/app/build/outputs/apk/release/app-release.apk + AAB=apps/mobile-rn/android/app/build/outputs/bundle/release/app-release.aab + test -f "$APK" + test -f "$AAB" + node scripts/ci/verify-mobile-build-config.mjs release \ + | tee apps/mobile-rn/android/app/build/outputs/release-build-config.json + node scripts/ci/create-mobile-release-evidence.mjs \ + --apk "$APK" \ + --aab "$AAB" \ + --bundletool "$RUNNER_TEMP/bundletool-all-1.18.3.jar" \ + --repository "$GITHUB_REPOSITORY" \ + --commit-sha "$GITHUB_SHA" \ + --tree-sha "$(git rev-parse "${GITHUB_SHA}^{tree}")" \ + --git-ref "$GITHUB_REF" \ + --workflow-identity "$GITHUB_WORKFLOW_REF" \ + --run-id "$GITHUB_RUN_ID" \ + --run-attempt "$GITHUB_RUN_ATTEMPT" \ + --runner-identity "$RUNNER_NAME:$RUNNER_OS:$RUNNER_ARCH" \ + --expected-admob-app-id "$D3RO_ADMOB_APP_ID" \ + --expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \ + --expected-version-name "$D3RO_VERSION_NAME" \ + --expected-version-code "$D3RO_VERSION_CODE" \ + --private-key apps/mobile-rn/android/app/release-evidence-private.pem \ + --snapshot-dir apps/mobile-rn/android/app/build/outputs/release-snapshot + VERIFIER_SHA256="$(sha256sum scripts/ci/verify-android-artifact.mjs | awk '{print $1}')" + BUNDLETOOL_SHA256="a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29" + node scripts/ci/prepare-mobile-release-publication.mjs \ + --source-root apps/mobile-rn/android/app/build/outputs/release-snapshot \ + --apk apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.apk \ + --aab apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.aab \ + --evidence apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-evidence.json \ + --public-key release/mobile-release-evidence-public.pem \ + --destination-dir apps/mobile-rn/android/app/build/outputs/release-publication \ + --expected-admob-app-id "$D3RO_ADMOB_APP_ID" \ + --expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \ + --expected-version-name "$D3RO_VERSION_NAME" \ + --expected-version-code "$D3RO_VERSION_CODE" \ + --expected-repository "$GITHUB_REPOSITORY" \ + --expected-commit-sha "$GITHUB_SHA" \ + --expected-tree-sha "$(git rev-parse "${GITHUB_SHA}^{tree}")" \ + --expected-git-ref "$GITHUB_REF" \ + --expected-workflow-identity "$GITHUB_WORKFLOW_REF" \ + --expected-run-id "$GITHUB_RUN_ID" \ + --expected-run-attempt "$GITHUB_RUN_ATTEMPT" \ + --expected-runner-identity "$RUNNER_NAME:$RUNNER_OS:$RUNNER_ARCH" \ + --expected-verifier-sha256 "$VERIFIER_SHA256" \ + --expected-bundletool-sha256 "$BUNDLETOOL_SHA256" + node scripts/ci/verify-android-app-links.mjs \ + --expected-play-app-signing-cert-sha256 "$PLAY_APP_SIGNING_CERT_SHA256" \ + --forbidden-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \ + | tee apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json + sha256sum apps/mobile-rn/android/app/build/outputs/release-publication/app-release.apk \ + apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab \ + | tee apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt + + - name: Remove Materialized Release Secrets + if: always() + shell: bash + run: rm -f apps/mobile-rn/android/app/release.keystore apps/mobile-rn/android/app/google-services.json apps/mobile-rn/android/app/release-evidence-private.pem + + - name: Upload Restricted Play Console AAB Handoff + uses: actions/upload-artifact@v4 + with: + name: android-play-console-handoff + path: | + apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab + apps/mobile-rn/android/app/build/outputs/release-publication/android-release-evidence.json + apps/mobile-rn/android/app/build/outputs/release-publication/android-publication-manifest.json + apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt + apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-verification.json + apps/mobile-rn/android/app/build/outputs/release-build-config.json + apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json + retention-days: 7 + if-no-files-found: error + + # ────────────────────────────────────────────────────────────────── + # 4. Build & Containerize Admin Dashboard # ────────────────────────────────────────────────────────────────── package-admin-docker: name: Build & Publish Admin Docker Image + needs: release-preflight runs-on: ubuntu-latest steps: - name: Checkout Code @@ -137,21 +462,54 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./Dockerfile.admin + file: ./apps/admin/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} # ────────────────────────────────────────────────────────────────── - # 4. Create GitHub Release & Upload Checksums + # 5. Create GitHub Release & Upload Checksums # ────────────────────────────────────────────────────────────────── publish-release: name: Publish Official GitHub Release - needs: [package-windows, package-macos, package-admin-docker] + needs: [package-windows, package-macos, package-android, package-admin-docker] runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Re-establish Trusted Release Identity + id: release-identity + shell: bash + env: + DISPATCH_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + git fetch --no-tags origin main + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + test "$GITHUB_REF" = "refs/heads/main" + test "$GITHUB_SHA" = "$(git rev-parse origin/main)" + VERSION_NAME="$DISPATCH_VERSION" + RELEASE_TAG="v$VERSION_NAME" + else + [[ "$GITHUB_REF" == refs/tags/v* ]] + VERSION_NAME="${GITHUB_REF_NAME#v}" + RELEASE_TAG="$GITHUB_REF_NAME" + git merge-base --is-ancestor "$GITHUB_SHA" origin/main + fi + SSOT_VERSION="$(node -p "require('./release/product-version.json').version")" + [[ "$VERSION_NAME" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] + test "$VERSION_NAME" = "$SSOT_VERSION" + test "$RELEASE_TAG" = "v$VERSION_NAME" + printf 'name=%s\n' "$VERSION_NAME" >> "$GITHUB_OUTPUT" + printf 'tag=%s\n' "$RELEASE_TAG" >> "$GITHUB_OUTPUT" + + - name: Setup Node.js 24 + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' - name: Download Windows Artifacts uses: actions/download-artifact@v4 @@ -167,9 +525,17 @@ jobs: - name: Generate SHA-256 Checksums run: | - cd release-dist - sha256sum * > SHA256SUMS.txt || shasum -a 256 * > SHA256SUMS.txt - cat SHA256SUMS.txt + set -euo pipefail + find release-dist -type f ! -name SHA256SUMS.txt -print0 \ + | sort -z \ + | xargs -0 sha256sum > release-dist/SHA256SUMS.txt + cat release-dist/SHA256SUMS.txt + + - name: Extract Canonical Release Notes + run: >- + node scripts/ci/extract-release-notes.mjs + --version "${{ steps.release-identity.outputs.name }}" + --output release-notes.md - name: Create GitHub Release uses: softprops/action-gh-release@v2 @@ -178,6 +544,10 @@ jobs: release-dist/* draft: false prerelease: false - generate_release_notes: true + body_path: release-notes.md + generate_release_notes: false + tag_name: ${{ steps.release-identity.outputs.tag }} + fail_on_unmatched_files: true + overwrite_files: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 09c4f78..4e261c5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,19 @@ out/ .expo/ .env .env.* +!.env.example *.log .DS_Store Thumbs.db .claude/settings.local.json -# Electron +# Electron release artifacts. Keep only release identity/public-key SSOT files. release/ +!/release/ +/release/* +!/release/product-version.json +!/release/android-release-identity.json +!/release/mobile-release-evidence-public.pem *.unpacked # Build @@ -52,3 +58,15 @@ venv/ # Whisper models (large files) resources/models/ +apps/mobile-rn/android/app/src/main/assets/models/ggml-tiny.bin +apps/mobile-rn/android/app/src/main/assets/index.android.bundle +apps/mobile-rn/.maestro-output/ +apps/mobile-rn/.maestro/*.junit.xml + +# Chrome / Playwright Profiles +.chrome-* +.playwright-* +.chrome-debug-profile/ +.chrome-interactive-profile/ +.chrome-playwright-profile/ +.playwright-oauth-profile/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9994da5..78a090e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,16 +5,19 @@ stages: - validate - test - build + - e2e - package - publish - deploy variables: - NODE_VERSION: "20" + NODE_VERSION: "24.19.0" PACKAGE_NAME: "d3ro-voice" default: - image: node:20-bookworm + image: node:24.19.0-bookworm + tags: + - build-linux-x64 before_script: - npm ci @@ -24,7 +27,16 @@ default: lint-and-typecheck: stage: validate script: - - npm run lint || true + - npm run version:check + - if [ -n "${CI_COMMIT_TAG:-}" ]; then node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG"; fi + - npm run release:metadata:test + - npm run security:secrets:test + - npm run security:secrets + - npm run release:mobile:boundary:test + - npm run release:mobile:config:test + - npm run release:mobile:build-config:test + - npm run release:play:assets + - npm run lint - npm run typecheck rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' @@ -48,6 +60,125 @@ test-unit: - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"' - if: '$CI_COMMIT_TAG' +api-server-tests: + stage: test + image: mcr.microsoft.com/dotnet/sdk:10.0.302-noble + before_script: [] + script: + - dotnet restore apps/api-server.Tests/D3ROVoice.Api.Tests.csproj + - dotnet test apps/api-server.Tests/D3ROVoice.Api.Tests.csproj --configuration Release --no-restore -p:StaticWebAssetsEnabled=false + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"' + - if: '$CI_COMMIT_TAG' + +edge-functions-quality: + stage: test + image: denoland/deno:2.8.1 + before_script: [] + script: + - for entrypoint in server/supabase/functions/*/index.ts; do deno check --config server/supabase/functions/deno.json "$entrypoint"; done + - deno test --config server/supabase/functions/deno.json --allow-read --allow-env server/supabase/functions + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"' + - if: '$CI_COMMIT_TAG' + +mobile-quality: + stage: test + before_script: + - npm ci + - npm --prefix apps/mobile-rn ci --workspaces=false + script: + - npm --prefix apps/mobile-rn run lint + - npm --prefix apps/mobile-rn run typecheck + - npm --prefix apps/mobile-rn run test:ci + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"' + - if: '$CI_COMMIT_TAG' + +mobile-android: + stage: build + image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36 + needs: + - mobile-quality + before_script: + - export ANDROID_HOME=/opt/android + - export ANDROID_SDK_ROOT=/opt/android + - curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz + - echo '14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647 /tmp/node.tar.xz' | sha256sum -c - + - mkdir -p /tmp/node24 + - tar -xJf /tmp/node.tar.xz -C /tmp/node24 --strip-components=1 + - export PATH="/tmp/node24/bin:$PATH" + - node --version | grep -Fx "v${NODE_VERSION}" + - export D3RO_VERSION_NAME="0.0.0-e2e.${CI_PIPELINE_IID}" + - export D3RO_VERSION_CODE="${CI_PIPELINE_IID}" + - sdkmanager "platforms;android-36" "build-tools;36.0.0" >/dev/null + - npm ci + - npm --prefix apps/mobile-rn ci --workspaces=false + script: + - node scripts/ci/prepare-whisper-model.mjs + - cd apps/mobile-rn/android + - ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest :app:assembleE2e -PreactNativeArchitectures=arm64-v8a,x86_64 --no-daemon + - cd "$CI_PROJECT_DIR" + - node scripts/ci/verify-mobile-build-config.mjs debug > apps/mobile-rn/android/app/build/outputs/debug-build-config.json + - node scripts/ci/verify-mobile-build-config.mjs e2e > apps/mobile-rn/android/app/build/outputs/e2e-build-config.json + - node scripts/ci/verify-android-artifact.mjs --mode debug --apk apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk > apps/mobile-rn/android/app/build/outputs/debug-artifact-evidence.json + - node scripts/ci/verify-android-artifact.mjs --mode e2e --apk apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk --expected-version-name "$D3RO_VERSION_NAME" --expected-version-code "$D3RO_VERSION_CODE" > apps/mobile-rn/android/app/build/outputs/e2e-artifact-evidence.json + - sha256sum apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk > apps/mobile-rn/android/app/build/outputs/android-ci.sha256 + artifacts: + when: always + expire_in: 1 day + paths: + - apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk + - apps/mobile-rn/android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk + - apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk + - apps/mobile-rn/android/app/build/outputs/android-ci.sha256 + - apps/mobile-rn/android/app/build/outputs/*-build-config.json + - apps/mobile-rn/android/app/build/outputs/*-artifact-evidence.json + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"' + - if: '$CI_COMMIT_TAG' + +mobile-emulator-e2e: + stage: e2e + image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36 + tags: + - android-emulator + - kvm + needs: + - job: mobile-android + artifacts: true + before_script: [] + script: + - export ANDROID_HOME=/opt/android + - export ANDROID_SDK_ROOT=/opt/android + - sdkmanager "platform-tools" "emulator" "platforms;android-35" "system-images;android-35;google_apis;x86_64" >/dev/null + - echo no | avdmanager create avd --force --name d3ro_ci_api35 --package "system-images;android-35;google_apis;x86_64" + - emulator -avd d3ro_ci_api35 -no-window -noaudio -no-boot-anim -gpu swiftshader_indirect -camera-back none & + - adb wait-for-device + - timeout 180 bash -c 'until [[ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d "\r")" == "1" ]]; do sleep 2; done' + - curl -fsSL https://github.com/mobile-dev-inc/maestro/releases/download/cli-2.7.0/maestro.zip -o /tmp/maestro.zip + - echo 'a4ccab6b604617e7aef6db4f885666056eabe5cfa32befaa3bc994041b8fcbb5 /tmp/maestro.zip' | sha256sum -c - + - unzip -q /tmp/maestro.zip -d /tmp/maestro + - export PATH="/tmp/maestro/maestro/bin:$PATH" + - bash scripts/ci/run-mobile-csprng-instrumentation.sh apps/mobile-rn/android/app/build/outputs/apk/debug/app-debug.apk apps/mobile-rn/android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk + - bash scripts/ci/run-mobile-emulator-gate.sh apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk + artifacts: + when: always + expire_in: 7 days + reports: + junit: apps/mobile-rn/.maestro/*.junit.xml + paths: + - apps/mobile-rn/.maestro/*.junit.xml + - apps/mobile-rn/.maestro-output/ + rules: + - if: '$D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$D3RO_ANDROID_EMULATOR_RUNNER == "true" && ($CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop")' + - if: '$D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/' + # ──────────────────────────────────────────────────────────────────── # Build Workspaces # ──────────────────────────────────────────────────────────────────── @@ -71,11 +202,11 @@ build-workspaces: package-windows: stage: package tags: - - windows - - electron + - build-win-x64 before_script: - npm ci script: + - node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG" - npm run build --workspace=@d3ro/desktop - cd apps/desktop - npx electron-builder --win --x64 --config electron-builder.yml @@ -95,11 +226,11 @@ package-windows: package-macos: stage: package tags: - - macos - - arm64 + - build-mac-arm64 before_script: - npm ci script: + - node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG" - npm run build --workspace=@d3ro/desktop - cd apps/desktop - npx electron-builder --mac --arm64 --config electron-builder.yml @@ -114,35 +245,196 @@ package-macos: rules: - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/' +# Protected, manually approved Play Console handoff. Configure the three +# *_FILE variables below as protected GitLab file variables scoped to the +# mobile-production-release environment. All other credentials must be +# protected, masked, and hidden project/group variables. +mobile-production-release: + stage: package + image: reactnativecommunity/react-native-android@sha256:24ca7ab5a70ec0b78a81bdc5eeea5924c2531531d53971b6f2321aff08446c36 + needs: + - job: lint-and-typecheck + artifacts: false + - job: test-unit + artifacts: false + - job: api-server-tests + artifacts: false + - job: edge-functions-quality + artifacts: false + - job: mobile-quality + artifacts: false + - job: mobile-android + artifacts: false + - job: mobile-emulator-e2e + artifacts: false + environment: + name: mobile-production-release + action: prepare + before_script: [] + script: + - | + set -euo pipefail + export ANDROID_HOME=/opt/android + export ANDROID_SDK_ROOT=/opt/android + curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz + echo '14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647 /tmp/node.tar.xz' | sha256sum -c - + mkdir -p /tmp/node24 + tar -xJf /tmp/node.tar.xz -C /tmp/node24 --strip-components=1 + export PATH="/tmp/node24/bin:$PATH" + node --version | grep -Fx "v${NODE_VERSION}" + git fetch --no-tags origin main + git merge-base --is-ancestor "$CI_COMMIT_SHA" origin/main + test "$CI_COMMIT_SHA" = "$(git rev-parse HEAD)" + test -z "$(git status --porcelain --untracked-files=all)" + SOURCE_TREE_SHA="$(git rev-parse "${CI_COMMIT_SHA}^{tree}")" + VERSION_NAME="$(node -p "require('./release/product-version.json').version")" + VERSION_CODE="$(node -p "require('./release/product-version.json').androidVersionCode")" + ANDROID_UPLOAD_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').uploadCertificateSha256")" + 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_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")" + test "$CI_COMMIT_TAG" = "v${VERSION_NAME}" + 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}$' + test "$VERSION_CODE" -le 2100000000 + test -s "$ANDROID_RELEASE_KEYSTORE_FILE" + test -s "$ANDROID_GOOGLE_SERVICES_JSON_FILE" + test -s "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_FILE" + install -m 600 "$ANDROID_RELEASE_KEYSTORE_FILE" apps/mobile-rn/android/app/release.keystore + install -m 600 "$ANDROID_GOOGLE_SERVICES_JSON_FILE" apps/mobile-rn/android/app/google-services.json + install -m 600 "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_FILE" apps/mobile-rn/android/app/release-evidence-private.pem + test -n "$ANDROID_RELEASE_STORE_PASSWORD" + test -n "$ANDROID_RELEASE_KEY_ALIAS" + test -n "$ANDROID_RELEASE_KEY_PASSWORD" + test -n "$D3RO_FIREBASE_EXPECTED_PROJECT_ID" + test -n "$D3RO_FIREBASE_EXPECTED_PROJECT_NUMBER" + test -n "$D3RO_FIREBASE_EXPECTED_MOBILESDK_APP_ID" + sdkmanager "platforms;android-36" "build-tools;36.0.0" >/dev/null + npm ci + npm --prefix apps/mobile-rn ci --workspaces=false + npm run security:secrets:test + npm run security:secrets + npm run release:mobile:boundary:test + npm run release:mobile:config:test + npm run release:mobile:build-config:test + npm run release:play:assets + npm --prefix apps/mobile-rn run lint + npm --prefix apps/mobile-rn run typecheck + npm --prefix apps/mobile-rn run test:ci + node scripts/ci/prepare-whisper-model.mjs + curl --fail --silent --show-error --location --output /tmp/bundletool.jar https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar + echo 'a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29 /tmp/bundletool.jar' | sha256sum -c - + export D3RO_RELEASE_STORE_FILE="$CI_PROJECT_DIR/apps/mobile-rn/android/app/release.keystore" + export D3RO_RELEASE_STORE_PASSWORD="$ANDROID_RELEASE_STORE_PASSWORD" + export D3RO_RELEASE_KEY_ALIAS="$ANDROID_RELEASE_KEY_ALIAS" + export D3RO_RELEASE_KEY_PASSWORD="$ANDROID_RELEASE_KEY_PASSWORD" + export D3RO_ADMOB_APP_ID="$ADMOB_APP_ID" + export D3RO_ADMOB_BANNER_UNIT_ID="$ADMOB_BANNER_UNIT_ID" + export D3RO_ADMOB_REWARDED_UNIT_ID="$ADMOB_REWARDED_UNIT_ID" + export D3RO_VERSION_NAME="$VERSION_NAME" + export D3RO_VERSION_CODE="$VERSION_CODE" + PLAY_APP_SIGNING_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').playAppSigningCertificateSha256")" + npm run release:mobile:config + cd apps/mobile-rn/android + ./gradlew :app:assembleRelease :app:bundleRelease -PreactNativeArchitectures=arm64-v8a --no-daemon + cd "$CI_PROJECT_DIR" + APK=apps/mobile-rn/android/app/build/outputs/apk/release/app-release.apk + AAB=apps/mobile-rn/android/app/build/outputs/bundle/release/app-release.aab + test -f "$APK" + test -f "$AAB" + node scripts/ci/verify-mobile-build-config.mjs release > apps/mobile-rn/android/app/build/outputs/release-build-config.json + node scripts/ci/create-mobile-release-evidence.mjs \ + --apk "$APK" \ + --aab "$AAB" \ + --bundletool /tmp/bundletool.jar \ + --repository "$CI_PROJECT_PATH" \ + --commit-sha "$CI_COMMIT_SHA" \ + --tree-sha "$SOURCE_TREE_SHA" \ + --git-ref "refs/tags/$CI_COMMIT_TAG" \ + --workflow-identity "gitlab-ci/mobile-production-release" \ + --run-id "$CI_JOB_ID" \ + --run-attempt "1" \ + --runner-identity "$CI_RUNNER_ID:$CI_RUNNER_REVISION" \ + --expected-admob-app-id "$D3RO_ADMOB_APP_ID" \ + --expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \ + --expected-version-name "$D3RO_VERSION_NAME" \ + --expected-version-code "$D3RO_VERSION_CODE" \ + --private-key apps/mobile-rn/android/app/release-evidence-private.pem \ + --snapshot-dir apps/mobile-rn/android/app/build/outputs/release-snapshot + VERIFIER_SHA256="$(sha256sum scripts/ci/verify-android-artifact.mjs | awk '{print $1}')" + BUNDLETOOL_SHA256="a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29" + node scripts/ci/prepare-mobile-release-publication.mjs \ + --source-root apps/mobile-rn/android/app/build/outputs/release-snapshot \ + --apk apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.apk \ + --aab apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.aab \ + --evidence apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-evidence.json \ + --public-key release/mobile-release-evidence-public.pem \ + --destination-dir apps/mobile-rn/android/app/build/outputs/release-publication \ + --expected-admob-app-id "$D3RO_ADMOB_APP_ID" \ + --expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \ + --expected-version-name "$D3RO_VERSION_NAME" \ + --expected-version-code "$D3RO_VERSION_CODE" \ + --expected-repository "$CI_PROJECT_PATH" \ + --expected-commit-sha "$CI_COMMIT_SHA" \ + --expected-tree-sha "$SOURCE_TREE_SHA" \ + --expected-git-ref "refs/tags/$CI_COMMIT_TAG" \ + --expected-workflow-identity "gitlab-ci/mobile-production-release" \ + --expected-run-id "$CI_JOB_ID" \ + --expected-run-attempt "1" \ + --expected-runner-identity "$CI_RUNNER_ID:$CI_RUNNER_REVISION" \ + --expected-verifier-sha256 "$VERIFIER_SHA256" \ + --expected-bundletool-sha256 "$BUNDLETOOL_SHA256" + node scripts/ci/verify-android-app-links.mjs \ + --expected-play-app-signing-cert-sha256 "$PLAY_APP_SIGNING_CERT_SHA256" \ + --forbidden-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \ + > apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json + sha256sum \ + apps/mobile-rn/android/app/build/outputs/release-publication/app-release.apk \ + apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab \ + > apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt + after_script: + - rm -f apps/mobile-rn/android/app/release.keystore apps/mobile-rn/android/app/google-services.json apps/mobile-rn/android/app/release-evidence-private.pem + artifacts: + access: maintainer + expire_in: 7 days + paths: + - apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab + - apps/mobile-rn/android/app/build/outputs/release-publication/android-release-evidence.json + - apps/mobile-rn/android/app/build/outputs/release-publication/android-publication-manifest.json + - apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt + - apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-verification.json + - apps/mobile-rn/android/app/build/outputs/release-build-config.json + - apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json + manual_confirmation: '검증된 production AAB를 생성해 Maintainer 전용 Play Console handoff로 보낼까요?' + rules: + - if: '$D3RO_MOBILE_PRODUCTION_RELEASE_ENABLED == "true" && $D3RO_ANDROID_EMULATOR_RUNNER == "true" && $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/' + when: manual + allow_failure: false + - when: never + # ──────────────────────────────────────────────────────────────────── # Publish Release (GitLab Package Registry + Release Page) # ──────────────────────────────────────────────────────────────────── publish-release: stage: publish - image: node:20-bookworm + image: node:24.19.0-bookworm needs: - job: package-windows artifacts: true - job: package-macos artifacts: true optional: true + - job: mobile-production-release + artifacts: false + optional: true script: - node scripts/ci/publish-gitlab-release.mjs rules: - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+.*$/' # ──────────────────────────────────────────────────────────────────── -# Deploy Admin Dashboard to NAS / Production Server +# Admin NAS deployment remains disabled until an immutable image, authenticated +# remote target, health check, and rollback path are configured. Do not revive +# the former DinD job: it only changed an ephemeral CI daemon and referenced a +# non-existent compose file, so it never deployed the NAS. # ──────────────────────────────────────────────────────────────────── -deploy-admin-nas: - stage: deploy - image: docker:24-cli - services: - - docker:24-dind - before_script: - - echo "$DOCKER_REGISTRY_PASSWORD" | docker login -u "$DOCKER_REGISTRY_USER" --password-stdin - script: - - docker build -t d3ro-voice-admin:latest -f Dockerfile.admin . - - docker compose -f docker-compose.prod.yml up -d admin - rules: - - if: '$CI_COMMIT_BRANCH == "main"' diff --git a/.nvmrc b/.nvmrc index 209e3ef..60ade1a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +24.19.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index adc95d2..8cae6c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cloud-optional backup (encrypted, opt-in) - Plugin system for custom pipelines +## [1.1.0] - 2026-08-29 + +### Added +- **Android/iOS product mobile app**: implemented account bootstrap and lifecycle, onboarding, recording and audio import, transcription, history, meetings, templates, memos, teams and invitations, notifications, knowledge, AI Talk, generated documents, data portability, and role-aware administration flows. +- **Mobile-native recording and intake**: added Android foreground recording with persistent controls and recovery, external `ACTION_SEND` audio/video intake, durable upload processing, and supported-device on-device Whisper transcription. +- **Server-verified mobile monetization**: added Google Play purchase and restore verification, entitlement reconciliation, AdMob rewarded SSV validation, and replay-safe reward receipts. +- **Generated-content safety controls**: added generation receipts, shared generative-AI safety instructions, and an authenticated report flow for owned AI-generated meeting documents. +- **Release verification tooling**: added Android artifact, App Links, production Firebase/AdMob configuration, Play asset, secret scanning, signed provenance, and publication-boundary checks. +- **Complete mobile icon set**: added canonical Android legacy/adaptive/monochrome launchers, the 512px Play icon, and all required iPhone, iPad, and App Store marketing icon slots. + +### Changed +- Unified mobile authentication and invitation links on the canonical `d3ro-voice` app scheme and added fail-closed verification for the HTTPS App Links contract. +- Hardened Supabase and mobile production configuration to fail closed when required public configuration is absent or does not match the release identity. +- Expanded CI coverage across Node.js 24, .NET 10, Deno Edge Functions, React Native tests, Android emulator gates, 16 KB page-size compatibility, and reproducible release evidence. +- Aligned account deletion guidance with external subscription cancellation requirements and centralized local sensitive-data purge on logout, account deletion, and session loss. +- Centralized product version `1.1.0`, Android version code `1010001`, release notes, updater metadata, and app-store changelogs under release SSOT checks. + +### Security +- Added atomic authorization and replay protection for teams, invitations, push delivery, transcription quotas, billing, ad rewards, administrative actions, data portability, and content reports. +- Removed synthetic success fallbacks from protected STT and AI paths; provider and configuration failures now return explicit failures without creating fabricated user data. +- Strengthened generated-document reporting so only the owner can report an existing document backed by immutable generation audit evidence. +- Removed the repository-exposed desktop license private key and prefix-only paid-tier activation; production now accepts only Ed25519 licenses signed by the rotated external key. + +### Fixed +- Corrected realtime Edge Function model routing and type checks for team and enterprise tiers. +- Removed the legacy `d3ro://` deep-link surface to prevent divergent authentication callback identities. + ## [0.2.1-alpha] - 2026-07-22 ### Fixed @@ -64,7 +91,7 @@ Midnight Glass v2 전면 재설계 + refactor-wave 대규모 코드 품질 정 --- -## [1.0.0] - 2026-04-06 +## [1.0.0] - 2026-08-20 ### Added diff --git a/Dockerfile.admin b/Dockerfile.admin index 772ea2a..35bd117 100644 --- a/Dockerfile.admin +++ b/Dockerfile.admin @@ -1,7 +1,7 @@ # Dockerfile.admin # Multi-stage production build for @d3ro/admin Next.js App -FROM node:20-alpine AS deps +FROM node:24.19.0-alpine AS deps WORKDIR /app RUN apk add --no-cache libc6-compat COPY package.json package-lock.json ./ @@ -11,7 +11,7 @@ COPY apps/desktop/package.json ./apps/desktop/package.json COPY apps/web/package.json ./apps/web/package.json RUN npm ci -FROM node:20-alpine AS builder +FROM node:24.19.0-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/packages ./packages @@ -23,7 +23,7 @@ ENV NODE_ENV production RUN npm run build --workspace=@d3ro/admin -FROM node:20-alpine AS runner +FROM node:24.19.0-alpine AS runner WORKDIR /app ENV NODE_ENV production diff --git a/apps/admin-swagger/openapi.json b/apps/admin-swagger/openapi.json index 2dc4ce6..385711a 100644 --- a/apps/admin-swagger/openapi.json +++ b/apps/admin-swagger/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "D3RO-VOICE Admin API", "description": "Admin CRM Edge Functions for user management, subscription CRUD, payment history, and audit logs.", - "version": "1.0.0" + "version": "1.1.0" }, "servers": [ { @@ -38,52 +38,159 @@ "Profile": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "name": { "type": "string", "nullable": true }, - "avatar_url": { "type": "string", "nullable": true }, - "locale": { "type": "string" }, - "tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] }, - "role": { "type": "string", "enum": ["user", "admin", "super_admin"] }, - "created_at": { "type": "string", "format": "date-time" }, - "updated_at": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "nullable": true + }, + "avatar_url": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string" + }, + "tier": { + "type": "string", + "enum": [ + "free", + "pro", + "pro_plus" + ] + }, + "role": { + "type": "string", + "enum": [ + "user", + "admin", + "super_admin" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } } }, "Subscription": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "user_id": { "type": "string", "format": "uuid" }, - "tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] }, - "status": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"] }, - "payment_provider": { "type": "string", "enum": ["none", "stripe", "payple"] }, - "current_period_start": { "type": "string", "format": "date-time", "nullable": true }, - "current_period_end": { "type": "string", "format": "date-time", "nullable": true }, - "overage_credits": { "type": "integer" }, - "admin_note": { "type": "string", "nullable": true }, - "renewal_failures": { "type": "integer" }, - "created_at": { "type": "string", "format": "date-time" }, - "updated_at": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "user_id": { + "type": "string", + "format": "uuid" + }, + "tier": { + "type": "string", + "enum": [ + "free", + "pro", + "pro_plus" + ] + }, + "status": { + "type": "string", + "enum": [ + "active", + "canceled", + "past_due", + "expired" + ] + }, + "payment_provider": { + "type": "string", + "enum": [ + "none", + "stripe", + "payple" + ] + }, + "current_period_start": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "current_period_end": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "overage_credits": { + "type": "integer" + }, + "admin_note": { + "type": "string", + "nullable": true + }, + "renewal_failures": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } } }, "AuditLog": { "type": "object", "properties": { - "id": { "type": "integer" }, - "admin_id": { "type": "string", "format": "uuid" }, - "admin_name": { "type": "string" }, - "action": { "type": "string" }, - "target_type": { "type": "string" }, - "target_id": { "type": "string", "format": "uuid" }, - "before_data": { "type": "object", "nullable": true }, - "after_data": { "type": "object", "nullable": true }, - "memo": { "type": "string" }, - "created_at": { "type": "string", "format": "date-time" } + "id": { + "type": "integer" + }, + "admin_id": { + "type": "string", + "format": "uuid" + }, + "admin_name": { + "type": "string" + }, + "action": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "target_id": { + "type": "string", + "format": "uuid" + }, + "before_data": { + "type": "object", + "nullable": true + }, + "after_data": { + "type": "object", + "nullable": true + }, + "memo": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } } }, "Error": { "type": "object", "properties": { - "error": { "type": "string" } + "error": { + "type": "string" + } } } } @@ -91,15 +198,57 @@ "paths": { "/admin-users": { "get": { - "tags": ["Users"], + "tags": [ + "Users" + ], "summary": "List or get user details", "description": "Admin+. Pass userId for single user detail, or omit for paginated list.", "parameters": [ - { "name": "userId", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "Specific user ID for detail view" }, - { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } }, - { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } }, - { "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Name search (ilike)" }, - { "name": "role", "in": "query", "schema": { "type": "string", "enum": ["user", "admin", "super_admin"] } } + { + "name": "userId", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Specific user ID for detail view" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Name search (ilike)" + }, + { + "name": "role", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "user", + "admin", + "super_admin" + ] + } + } ], "responses": { "200": { @@ -111,17 +260,32 @@ { "type": "object", "properties": { - "profiles": { "type": "array", "items": { "$ref": "#/components/schemas/Profile" } }, - "total": { "type": "integer" }, - "page": { "type": "integer" }, - "limit": { "type": "integer" } + "profiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Profile" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "limit": { + "type": "integer" + } } }, { "type": "object", "properties": { - "profile": { "$ref": "#/components/schemas/Profile" }, - "subscription": { "$ref": "#/components/schemas/Subscription" } + "profile": { + "$ref": "#/components/schemas/Profile" + }, + "subscription": { + "$ref": "#/components/schemas/Subscription" + } } } ] @@ -129,11 +293,22 @@ } } }, - "403": { "description": "Not admin", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "403": { + "description": "Not admin", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "patch": { - "tags": ["Users"], + "tags": [ + "Users" + ], "summary": "Change user role", "description": "Super admin only. Changes both auth.users.app_metadata.role and profiles.role.", "requestBody": { @@ -142,39 +317,110 @@ "application/json": { "schema": { "type": "object", - "required": ["userId", "newRole", "memo"], + "required": [ + "userId", + "newRole", + "memo" + ], "properties": { - "userId": { "type": "string", "format": "uuid" }, - "newRole": { "type": "string", "enum": ["user", "admin", "super_admin"] }, - "memo": { "type": "string", "description": "Required reason for audit log" } + "userId": { + "type": "string", + "format": "uuid" + }, + "newRole": { + "type": "string", + "enum": [ + "user", + "admin", + "super_admin" + ] + }, + "memo": { + "type": "string", + "description": "Required reason for audit log" + } } } } } }, "responses": { - "200": { "description": "Role changed successfully" }, - "403": { "description": "Not super_admin" } + "200": { + "description": "Role changed successfully" + }, + "403": { + "description": "Not super_admin" + } } } }, "/admin-subscriptions": { "get": { - "tags": ["Subscriptions"], + "tags": [ + "Subscriptions" + ], "summary": "List or get subscription details", "parameters": [ - { "name": "userId", "in": "query", "schema": { "type": "string", "format": "uuid" } }, - { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } }, - { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } }, - { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"] } }, - { "name": "tier", "in": "query", "schema": { "type": "string", "enum": ["free", "pro", "pro_plus"] } } + { + "name": "userId", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "active", + "canceled", + "past_due", + "expired" + ] + } + }, + { + "name": "tier", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "free", + "pro", + "pro_plus" + ] + } + } ], "responses": { - "200": { "description": "Subscription list or detail" } + "200": { + "description": "Subscription list or detail" + } } }, "post": { - "tags": ["Subscriptions"], + "tags": [ + "Subscriptions" + ], "summary": "Create subscription (VIP grant / record recovery)", "description": "Super admin only.", "requestBody": { @@ -183,30 +429,74 @@ "application/json": { "schema": { "type": "object", - "required": ["userId", "tier", "memo"], + "required": [ + "userId", + "tier", + "memo" + ], "properties": { - "userId": { "type": "string", "format": "uuid" }, - "tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] }, - "status": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"], "default": "active" }, - "currentPeriodEnd": { "type": "string", "format": "date-time" }, - "adminNote": { "type": "string" }, - "memo": { "type": "string" } + "userId": { + "type": "string", + "format": "uuid" + }, + "tier": { + "type": "string", + "enum": [ + "free", + "pro", + "pro_plus" + ] + }, + "status": { + "type": "string", + "enum": [ + "active", + "canceled", + "past_due", + "expired" + ], + "default": "active" + }, + "currentPeriodEnd": { + "type": "string", + "format": "date-time" + }, + "adminNote": { + "type": "string" + }, + "memo": { + "type": "string" + } } } } } }, "responses": { - "201": { "description": "Subscription created" }, - "409": { "description": "Subscription already exists" } + "201": { + "description": "Subscription created" + }, + "409": { + "description": "Subscription already exists" + } } }, "patch": { - "tags": ["Subscriptions"], + "tags": [ + "Subscriptions" + ], "summary": "Update subscription", "description": "Super admin only.", "parameters": [ - { "name": "userId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "name": "userId", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -214,29 +504,67 @@ "application/json": { "schema": { "type": "object", - "required": ["memo"], + "required": [ + "memo" + ], "properties": { - "tier": { "type": "string", "enum": ["free", "pro", "pro_plus"] }, - "status": { "type": "string", "enum": ["active", "canceled", "past_due", "expired"] }, - "currentPeriodEnd": { "type": "string", "format": "date-time" }, - "overageCredits": { "type": "integer" }, - "adminNote": { "type": "string" }, - "memo": { "type": "string" } + "tier": { + "type": "string", + "enum": [ + "free", + "pro", + "pro_plus" + ] + }, + "status": { + "type": "string", + "enum": [ + "active", + "canceled", + "past_due", + "expired" + ] + }, + "currentPeriodEnd": { + "type": "string", + "format": "date-time" + }, + "overageCredits": { + "type": "integer" + }, + "adminNote": { + "type": "string" + }, + "memo": { + "type": "string" + } } } } } }, "responses": { - "200": { "description": "Subscription updated" } + "200": { + "description": "Subscription updated" + } } }, "delete": { - "tags": ["Subscriptions"], + "tags": [ + "Subscriptions" + ], "summary": "Soft-delete subscription", "description": "Super admin only. Sets status=expired, tier=free.", "parameters": [ - { "name": "userId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "name": "userId", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -244,27 +572,54 @@ "application/json": { "schema": { "type": "object", - "required": ["memo"], + "required": [ + "memo" + ], "properties": { - "memo": { "type": "string" } + "memo": { + "type": "string" + } } } } } }, "responses": { - "200": { "description": "Subscription soft-deleted" } + "200": { + "description": "Subscription soft-deleted" + } } } }, "/admin-payments": { "get": { - "tags": ["Payments"], + "tags": [ + "Payments" + ], "summary": "Get payment history for a user", "description": "Admin+. Returns DB subscription data + audit logs. Pass source=payple for Payple API history.", "parameters": [ - { "name": "userId", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } }, - { "name": "source", "in": "query", "schema": { "type": "string", "enum": ["db", "payple"] }, "description": "Add 'payple' to also fetch from Payple API" } + { + "name": "userId", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "source", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "db", + "payple" + ] + }, + "description": "Add 'payple' to also fetch from Payple API" + } ], "responses": { "200": { @@ -274,10 +629,23 @@ "schema": { "type": "object", "properties": { - "subscription": { "$ref": "#/components/schemas/Subscription" }, - "auditLogs": { "type": "array", "items": { "$ref": "#/components/schemas/AuditLog" } }, - "paypleHistory": { "type": "object", "description": "Payple API response (when source=payple)" }, - "paypleError": { "type": "string", "description": "Error message if Payple API call failed" } + "subscription": { + "$ref": "#/components/schemas/Subscription" + }, + "auditLogs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLog" + } + }, + "paypleHistory": { + "type": "object", + "description": "Payple API response (when source=payple)" + }, + "paypleError": { + "type": "string", + "description": "Error message if Payple API call failed" + } } } } @@ -288,18 +656,81 @@ }, "/admin-audit-log": { "get": { - "tags": ["Audit Log"], + "tags": [ + "Audit Log" + ], "summary": "List or get audit log entries", "description": "Admin+. Pass id for single entry detail.", "parameters": [ - { "name": "id", "in": "query", "schema": { "type": "integer" }, "description": "Specific log entry ID" }, - { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } }, - { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } }, - { "name": "target_type", "in": "query", "schema": { "type": "string", "enum": ["subscription", "profile"] } }, - { "name": "admin_id", "in": "query", "schema": { "type": "string", "format": "uuid" } }, - { "name": "target_id", "in": "query", "schema": { "type": "string", "format": "uuid" } }, - { "name": "from", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Start date (YYYY-MM-DD)" }, - { "name": "to", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "End date (YYYY-MM-DD)" } + { + "name": "id", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Specific log entry ID" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20 + } + }, + { + "name": "target_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "subscription", + "profile" + ] + } + }, + { + "name": "admin_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "target_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Start date (YYYY-MM-DD)" + }, + { + "name": "to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "End date (YYYY-MM-DD)" + } ], "responses": { "200": { @@ -311,17 +742,32 @@ { "type": "object", "properties": { - "logs": { "type": "array", "items": { "$ref": "#/components/schemas/AuditLog" } }, - "total": { "type": "integer" }, - "page": { "type": "integer" }, - "limit": { "type": "integer" } + "logs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuditLog" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "limit": { + "type": "integer" + } } }, { "type": "object", "properties": { - "log": { "$ref": "#/components/schemas/AuditLog" }, - "admin": { "$ref": "#/components/schemas/Profile" } + "log": { + "$ref": "#/components/schemas/AuditLog" + }, + "admin": { + "$ref": "#/components/schemas/Profile" + } } } ] diff --git a/apps/admin/Dockerfile b/apps/admin/Dockerfile index 743fe40..5eeff6a 100644 --- a/apps/admin/Dockerfile +++ b/apps/admin/Dockerfile @@ -1,5 +1,5 @@ # apps/admin/Dockerfile -FROM node:24-alpine AS base +FROM node:24.19.0-alpine AS base FROM base AS builder WORKDIR /app diff --git a/apps/admin/package.json b/apps/admin/package.json index 2f675b2..c1e58ae 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/admin", - "version": "1.0.0", + "version": "1.1.0", "private": true, "description": "D3RO Voice Admin CRM — SaaS 관리 도구", "scripts": { diff --git a/apps/admin/src/app/(admin)/releases/page.tsx b/apps/admin/src/app/(admin)/releases/page.tsx index 258619c..b915a94 100644 --- a/apps/admin/src/app/(admin)/releases/page.tsx +++ b/apps/admin/src/app/(admin)/releases/page.tsx @@ -2,7 +2,6 @@ // D3RO Voice Admin CRM — Release & Distribution Hub (Forgejo live feed) import { Box, Typography, Button } from '@mui/material' -import Link from 'next/link' import { DoubleBezelCard, TactileBadge } from '@d3ro/ui/components/ds' import { C, FONT_SANS, FONT_MONO, panelSx, tableSx, statusBadgeSx } from '@/lib/console-theme' import { requireManager } from '@/lib/admin-guard' @@ -91,7 +90,6 @@ function HeaderBar({ feedLive, repoHtmlUrl }: { feedLive: boolean; repoHtmlUrl: