Compare commits
3 commits
c3ddd36c6f
...
359b244dc9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
359b244dc9 | ||
|
|
035d0a76f5 | ||
|
|
49a4c97923 |
37 changed files with 168 additions and 106 deletions
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -13,6 +13,38 @@ 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.2.0] - 2026-09-16
|
||||
|
||||
> Published from an annotated tag through CI. Installer and update metadata are
|
||||
> served by the canonical Forgejo feed; no binaries are committed to this repository.
|
||||
|
||||
### Added
|
||||
- **Canonical desktop release channel**: desktop auto-update now reads one Forgejo Generic Package Registry feed, published by a version-agnostic publisher (`scripts/ci/publish-forgejo-release.mjs`) from tag-triggered Forgejo Actions, GitLab, and GitHub pipelines. The GitLab registry stays a legacy mirror for pre-Forgejo installs.
|
||||
- **Update policy SSOT** (`release/update-policy.json`): channels (`latest`/`beta`/`alpha`), minimum supported version, forced install, full-installer thresholds, staged rollout percentage, and a remote kill switch, enforced at runtime by `apps/desktop/src/main/update-policy.ts`.
|
||||
- **Dictionary import/export on desktop and web**: round-trip import with per-entry conflict reporting, a web dictionary client, and an expanded knowledge add form.
|
||||
- **Multi-transport push delivery**: Web Push (VAPID) and token-based Apple Push (APNs) transports join Firebase Cloud Messaging, with a Cloudflare Worker cron drain over an outbox table.
|
||||
- **Team activity feed**: team activity events, migration, and the web feed component.
|
||||
- **Shared entitlement gating** in `@d3ro/core` for free/paid feature boundaries.
|
||||
- **Infrastructure map** (`docs/map/`) documenting the infrastructure and feature status per platform, with an update protocol so feature work and the map stay in step.
|
||||
- 21 unit tests for update policy decisions and feed helpers, plus tests for dictionary I/O, entitlements, push drain, Web Push, and APNs payloads.
|
||||
|
||||
### Changed
|
||||
- Desktop runtime updater feed moved from GitLab project 1172 to the canonical Forgejo registry; `electron-builder.yml`, the metadata verifier, and both CI publishers now enforce the canonical/mirror split.
|
||||
- Release metadata verifier self-test expanded to negative cases covering the feed contract, policy schema, and Forgejo publisher invariants.
|
||||
- The canonical publisher refuses to re-publish a version whose registry assets already hold different bytes, so a same-version re-release fails closed instead of overwriting a shipped installer.
|
||||
- Landing site and web console download centers now link the canonical Forgejo feed instead of repository-local installer paths, which are not part of any deploy artifact.
|
||||
- Admin console data views (models, pipelines, users, subscriptions, audit log) read live back-office data, with a unified sidebar and console theme.
|
||||
- Desktop settings, ad surfaces, license, and meeting-export UI aligned on the shared theme tokens; meeting export filenames now go through one sanitizer.
|
||||
- Developer-only automation, scratch captures, .NET build output, and Playwright run output left the release checkout.
|
||||
- Product version moved to `1.2.0` with Android version code and iOS build number `1020001`.
|
||||
|
||||
### Fixed
|
||||
- Desktop configuration writes fail explicitly when the config store is unavailable instead of falling back to a throwaway in-memory store.
|
||||
- Speech provider and model failures reach the UI as errors instead of surfacing as empty transcriptions.
|
||||
- Removed the Forgejo release-publishing prohibition; the legacy hardcoded-version script is replaced by a version-gated publisher.
|
||||
- Dropped a stale admin bundle from the API server web root.
|
||||
- Mobile team, meeting, memo, template, command, and dictionary screens follow the server contract, and report submission no longer hangs without confirming to the user.
|
||||
|
||||
## [1.1.0] - 2026-08-29
|
||||
|
||||
### Added
|
||||
|
|
@ -22,13 +54,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **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.
|
||||
- **Canonical desktop release channel**: desktop auto-update now reads one Forgejo Generic Package Registry feed, published by a version-agnostic publisher (`scripts/ci/publish-forgejo-release.mjs`) from a tag-triggered Forgejo Actions workflow. GitLab CI and GitHub Actions converge on the same publisher, and the GitLab registry stays a legacy mirror for pre-Forgejo installs.
|
||||
- **Update policy SSOT** (`release/update-policy.json`): channels (`latest`/`beta`/`alpha`), minimum supported version, forced install, full-installer thresholds, staged rollout percentage, and a remote kill switch, enforced at runtime by `apps/desktop/src/main/update-policy.ts`.
|
||||
- **Dictionary import/export on desktop and web**: round-trip import with per-entry conflict reporting, a web dictionary client, and an expanded knowledge add form.
|
||||
- **Multi-transport push delivery**: Web Push (VAPID) and token-based Apple Push (APNs) transports join Firebase Cloud Messaging, with a Cloudflare Worker cron drain over an outbox table.
|
||||
- **Team activity feed**: team activity events, migration, and the web feed component.
|
||||
- **Shared entitlement gating** in `@d3ro/core` for free/paid feature boundaries.
|
||||
- 21 unit tests for update policy decisions and feed helpers, plus tests for dictionary I/O, entitlements, push drain, Web Push, and APNs payloads.
|
||||
|
||||
### 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.
|
||||
|
|
@ -39,18 +64,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Finalized benchmarked `ko-KR`/`en-US` Play listing copy and a Console-previewed
|
||||
1024×500 canonical feature graphic with preserved generation prompt, source,
|
||||
output, and hashes.
|
||||
- Desktop runtime updater feed moved from GitLab project 1172 to the canonical
|
||||
Forgejo registry; `electron-builder.yml`, the metadata verifier, and both CI
|
||||
publishers now enforce the canonical/mirror split.
|
||||
- Release metadata verifier self-test expanded to 13 negative cases covering the
|
||||
feed contract, policy schema, and Forgejo publisher invariants.
|
||||
- Landing site and web console download centers now link the canonical Forgejo feed
|
||||
instead of repository-local installer paths, which are not part of any deploy
|
||||
artifact.
|
||||
- Admin console data views (models, pipelines, users, subscriptions, audit log) read
|
||||
live back-office data, with a unified sidebar and console theme.
|
||||
- Desktop settings, ad surfaces, license, and meeting-export UI aligned on the shared
|
||||
theme tokens; meeting export filenames now go through one sanitizer.
|
||||
|
||||
### Security
|
||||
- Added atomic authorization and replay protection for teams, invitations, push delivery, transcription quotas, billing, ad rewards, administrative actions, data portability, and content reports.
|
||||
|
|
@ -62,11 +75,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### 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.
|
||||
- Removed the Forgejo release-publishing prohibition; the legacy hardcoded-version
|
||||
script is replaced by a version-gated publisher.
|
||||
- Dropped a stale admin bundle from the API server web root, and stopped tracking
|
||||
.NET build output, Playwright run output, and developer-only automation scripts in
|
||||
the release checkout.
|
||||
|
||||
## [0.2.1-alpha] - 2026-07-22
|
||||
|
||||
|
|
|
|||
|
|
@ -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.1.0"
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/admin",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice Admin CRM — SaaS 관리 도구",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.2.0</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/desktop",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"productName": "d3ro-voice",
|
||||
"description": "로컬 AI 음성 어시스턴트 (Electron)",
|
||||
"main": "./out/main/index.js",
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ def versionSettingsValid = configuredVersionName != null &&
|
|||
configuredVersionName ==~ strictSemver &&
|
||||
configuredVersionCodeValue != null &&
|
||||
configuredVersionCodeValue <= 2100000000L
|
||||
def resolvedVersionName = versionSettingsValid ? configuredVersionName : "1.1.0"
|
||||
def resolvedVersionCode = versionSettingsValid ? configuredVersionCodeValue.toInteger() : 1010001
|
||||
def resolvedVersionName = versionSettingsValid ? configuredVersionName : "1.2.0"
|
||||
def resolvedVersionCode = versionSettingsValid ? configuredVersionCodeValue.toInteger() : 1020001
|
||||
|
||||
def requiredReleaseSettings = [
|
||||
D3RO_RELEASE_STORE_FILE: releaseStoreFilePath,
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@
|
|||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1010001;
|
||||
CURRENT_PROJECT_VERSION = 1020001;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = D3ROVoice/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
|
|
@ -265,7 +265,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
|
@ -287,14 +287,14 @@
|
|||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1010001;
|
||||
CURRENT_PROJECT_VERSION = 1020001;
|
||||
INFOPLIST_FILE = D3ROVoice/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Team, meeting, memo, template, command, and dictionary screens now use server responses directly, so lists and edits stay in sync. Reporting a generated document confirms completion instead of hanging.
|
||||
|
|
@ -0,0 +1 @@
|
|||
팀·회의·메모·템플릿·명령·사전 화면이 서버 응답을 그대로 사용해 목록과 수정 내용이 어긋나지 않습니다. 생성 문서 신고가 멈추지 않고 완료 확인을 표시합니다.
|
||||
14
apps/mobile-rn/package-lock.json
generated
14
apps/mobile-rn/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@d3ro/mobile-rn",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@d3ro/mobile-rn",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@d3ro/api-client": "file:../../packages/api-client",
|
||||
"@d3ro/core": "file:../../packages/core",
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
},
|
||||
"../..": {
|
||||
"name": "d3ro-voice-monorepo",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"apps/desktop",
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
},
|
||||
"../../packages/api-client": {
|
||||
"name": "@d3ro/api-client",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@d3ro/core": "*",
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
},
|
||||
"../../packages/core": {
|
||||
"name": "@d3ro/core",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"docx": "^9.6.1"
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
},
|
||||
"../../packages/i18n": {
|
||||
"name": "@d3ro/i18n",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
},
|
||||
"../../packages/ui-native": {
|
||||
"name": "@d3ro/ui-native",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/react": "*"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/mobile-rn",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/web",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 웹 앱 — Next.js 기반 SaaS 인터페이스",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
DESKTOP_FEED_URL,
|
||||
DESKTOP_RELEASE_HUB_URL,
|
||||
DESKTOP_RELEASES_URL,
|
||||
DESKTOP_RELEASE_DATE,
|
||||
DESKTOP_VERSION,
|
||||
DESKTOP_WINDOWS_INSTALLER_FILENAME,
|
||||
DESKTOP_WINDOWS_INSTALLER_URL,
|
||||
|
|
@ -539,7 +540,7 @@ export default function DownloadPage(): React.ReactElement {
|
|||
fontWeight: 500,
|
||||
}}
|
||||
/>
|
||||
<Typography sx={{ color: d3roPalette.text.label, fontSize: '12px', fontFamily: 'monospace' }}>2026-08-29</Typography>
|
||||
<Typography sx={{ color: d3roPalette.text.label, fontSize: '12px', fontFamily: 'monospace' }}>{DESKTOP_RELEASE_DATE}</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
component="a"
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ export function Sidebar(): React.ReactElement {
|
|||
</PhosphorText>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: '10px', fontFamily: 'ui-monospace, monospace', color: d3roPalette.text.muted }}>
|
||||
v1.1.0
|
||||
v1.2.0
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
// 설치 파일은 canonical Forgejo feed에서만 배포한다. 웹/사이트 빌드 산출물에는
|
||||
// 설치 바이너리가 포함되지 않으므로 `/releases/...` 로컬 경로는 배포 환경에서 404다.
|
||||
|
||||
export const DESKTOP_VERSION = '1.1.0'
|
||||
export const DESKTOP_VERSION = '1.2.0'
|
||||
|
||||
/** 릴리스 게시일. `release/product-version.json`의 releaseDate와 같아야 한다. */
|
||||
export const DESKTOP_RELEASE_DATE = '2026-09-16'
|
||||
|
||||
const FORGEJO_ORIGIN = 'https://git.chanpaca.net'
|
||||
const FORGEJO_OWNER = 'yunchan'
|
||||
|
|
|
|||
|
|
@ -6,35 +6,36 @@
|
|||
|
||||
| 항목 | 정본 | 현재 판정 |
|
||||
|---|---|---|
|
||||
| 제품 버전 | `release/product-version.json`: `1.1.0` | source SSOT 확정 |
|
||||
| Android | versionCode `1010001` | production AAB 미생성 |
|
||||
| iOS | build `1010001` | production archive 미검증 |
|
||||
| 제품 버전 | `release/product-version.json`: `1.2.0` | source SSOT 확정 |
|
||||
| Android | versionCode `1020001` | production AAB 미생성 |
|
||||
| iOS | build `1020001` | production archive 미검증 |
|
||||
| Android upload key | alias `d3ro-upload-20260821`, cert SHA-256 `4F:AC:69:24:...:15:2B:54` | external PKCS12·user-only ACL·Credential Manager·private-key readback GREEN; CI secret·복구 백업·AAB signer 대조 대기 |
|
||||
| release evidence | Ed25519 public `release/mobile-release-evidence-public.pem`, keyId `2797d3e6...4a890b7f` | external private key ACL·roundtrip GREEN; CI private-key secret·복구 백업 대기 |
|
||||
| desktop offline license | Ed25519 public `apps/desktop/resources/license/production-public.pem`, keyId `5c52b765...81a887f` | 새 전용 keypair·external private ACL·roundtrip·desktop production build GREEN; admin `ADMIN_LICENSE_PRIVATE_KEY` secret 주입 대기 |
|
||||
| Windows Authenticode | external public-trust code-signing certificate | 현재 local `1.1.0` installer·unpacked app은 `NotSigned`; production PFX·CI secret·signed artifact GREEN 전까지 게시 금지 |
|
||||
| Windows Authenticode | external public-trust code-signing certificate | 현재 local installer·unpacked app은 `NotSigned`; production PFX·CI secret·signed artifact GREEN 전까지 게시 금지 |
|
||||
| Firebase | Console `u/0`, `u/1` 모두 D3RO project 없음 | 사용자 승인 후 project·Android app 생성 필요 |
|
||||
| AdMob | app `ca-app-pub-1039714767792854~6427959892`; banner `/9840591290`; rewarded `/2255790918` | SSOT 확정. `검토 필요`·`광고 게재 제한`·store 미연결·결제 프로필 미완료 |
|
||||
| updater feed (canonical) | `https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/latest` | Forgejo Generic Registry. GitLab project 1172은 legacy mirror |
|
||||
| release notes | `CHANGELOG.md` `## [1.1.0]` | 태그 전 확정·검증 필수 |
|
||||
| release notes | `CHANGELOG.md` `## [1.2.0]` | 태그 전 확정·검증 필수 |
|
||||
| 직전 게시본 | Forgejo Release `v1.1.0` (2026-09-15 게시, unsigned installer 포함) | 불변 태그. `1.2.0`은 이를 대체하는 forward-fix |
|
||||
|
||||
source SSOT와 live updater metadata의 버전이 다르므로 아직 `1.1.0` 배포 완료가 아니다.
|
||||
live canonical feed(`git.chanpaca.net/.../d3ro-voice/latest`)의 `latest.yml`은 현재 `1.1.0`을 보고한다. `1.2.0` 태그 파이프라인이 GREEN이 되면 그 값이 올라간다.
|
||||
|
||||
## desktop 릴리스 파이프라인
|
||||
|
||||
```text
|
||||
authoritative release commit
|
||||
→ version/check/test/build GREEN
|
||||
→ annotated tag v1.1.0
|
||||
→ annotated tag v1.2.0
|
||||
→ package-windows (build-win-x64)
|
||||
→ package-macos (build-mac-arm64)
|
||||
→ publish-release (build-linux-x64)
|
||||
├─ publish-forgejo-release.mjs ← canonical
|
||||
│ ├─ Forgejo Generic Registry /d3ro-voice/1.1.0/ (버전별 보존)
|
||||
│ ├─ Forgejo Generic Registry /d3ro-voice/1.2.0/ (버전별 보존)
|
||||
│ ├─ Forgejo Generic Registry /d3ro-voice/latest/ (updater feed + update-policy.json)
|
||||
│ └─ Forgejo Release + CHANGELOG notes + 자산 첨부
|
||||
└─ publish-gitlab-release.mjs ← legacy mirror (pre-Forgejo 설치본)
|
||||
├─ GitLab Generic Registry /d3ro-voice/1.1.0/
|
||||
├─ GitLab Generic Registry /d3ro-voice/1.2.0/
|
||||
├─ GitLab Generic Registry /d3ro-voice/latest/
|
||||
└─ GitLab Release
|
||||
```
|
||||
|
|
@ -49,7 +50,7 @@ authoritative release commit
|
|||
포함되지 않는다. 사이트·웹 다운로드 센터는 로컬 경로가 아니라 feed URL을
|
||||
링크한다. (역사적 `1.0.0` 자산만 추적 상태로 남아 있다.)
|
||||
|
||||
- `scripts/ci/sync-version.mjs --check --tag v1.1.0`는 태그, `release/product-version.json`, package/lockfile, Android/iOS 버전 면의 일치를 fail-closed로 검증한다.
|
||||
- `scripts/ci/sync-version.mjs --check --tag v1.2.0`는 태그, `release/product-version.json`, package/lockfile, Android/iOS 버전 면의 일치를 fail-closed로 검증한다.
|
||||
- `scripts/ci/verify-release-metadata.mjs`는 배포 메타데이터와 CI/publisher 계약을 검증한다.
|
||||
- 같은 gate는 desktop license public key가 Ed25519이고 `release/product-version.json`의 `desktopLicensePublicKeyId`와 일치하는지 검증한다. `electron.vite.config.ts`는 이 파일을 직접 읽으므로 누락·손상된 키로는 build가 시작되지 않는다.
|
||||
- `scripts/ci/publish-forgejo-release.mjs`는 canonical이다. 버전별 패키지를 먼저 올리고, `latest`에서 설치 자산 참조를 검증한 뒤 `latest.yml`과 `update-policy.json`을 마지막에 게시하고 공개 URL에서 재검증한다. `scripts/ci/publish-gitlab-release.mjs`는 legacy mirror로 동일 자산을 GitLab에도 올린다.
|
||||
|
|
@ -83,7 +84,10 @@ https://gitlab.twentyoz.kr:8443/api/v4/projects/1172/packages/generic/d3ro-voice
|
|||
6. installer와 `win-unpacked/D3RO Voice.exe` 모두 external public-trust certificate의 Authenticode `Valid`이고, signer subject가 protected CI identity와 정확히 일치한다.
|
||||
7. 이전 실제 설치본이 feed를 탐지하고, 다운로드·재시작·버전 상승을 끝까지 완료한다.
|
||||
|
||||
2026-08-29 live `latest.yml`의 버전은 `0.2.1-alpha`다(legacy GitLab feed). 이는 updater endpoint가 응답한다는 증거일 뿐 `1.1.0` 게시 증거가 아니다.
|
||||
2026-09-16 live canonical `latest.yml`은 `1.1.0`을 보고한다(2026-09-15 hand-publish).
|
||||
그 installer는 Authenticode 서명이 없으므로 정책상 정상 게시본이 아니다. `1.2.0`은
|
||||
서명 gate를 통과한 CI build로 이 값을 대체하는 forward-fix다. legacy GitLab feed는
|
||||
여전히 `0.2.1-alpha`다.
|
||||
|
||||
## 업데이트 채널과 메이저/증분 정책
|
||||
|
||||
|
|
@ -106,14 +110,14 @@ https://gitlab.twentyoz.kr:8443/api/v4/projects/1172/packages/generic/d3ro-voice
|
|||
- **회수(rollback)**: `stagingPercentage`를 낮추거나 `killSwitch`를 켠다. 이미 배포된 버전은 되돌리지 않고 더 높은 patch로 forward-fix한다.
|
||||
|
||||
|
||||
## `1.1.0` 릴리스 절차
|
||||
## `1.2.0` 릴리스 절차
|
||||
|
||||
1. `release/product-version.json`의 version/build 값과 모든 버전 면을 `npm run version:check`로 대조한다.
|
||||
2. `CHANGELOG.md` `## [1.1.0] - 2026-08-29` 섹션을 사용자 변경점 중심으로 확정한다. publisher는 이 섹션이 없으면 실패해야 한다.
|
||||
2. `CHANGELOG.md` `## [1.2.0] - 2026-09-16` 섹션을 사용자 변경점 중심으로 확정한다. publisher는 이 섹션이 없으면 실패해야 한다.
|
||||
3. dirty/untracked 작업을 임의로 reset·clean하지 말고, release 범위만 검토 가능한 authoritative commit으로 보존한다.
|
||||
4. 같은 commit에서 lint, typecheck, test, build, release metadata·security·artifact gate를 전부 GREEN으로 만든다. Windows는 production Authenticode PFX를 주입한 CI build와 `verify-windows-release-artifact.ps1` GREEN이 필수다.
|
||||
5. desktop offline license를 제공한다면 external private key를 admin의 `ADMIN_LICENSE_PRIVATE_KEY` secret로 주입하고, 저장소 public key와 sign/verify roundtrip 및 발급 감사 로그를 확인한다.
|
||||
6. 이전 버전보다 높은 annotated 태그 `v1.1.0`을 생성해 push한다. `npm run release:tag -- --dry-run`으로 검증한 뒤 `npm run release:tag`(GPG 사용 시 `-- --sign`)와 `git push origin v1.1.0`를 실행한다. 태그는 불변이며 게이트를 시작하는 후속 단계지 검증을 대체하지 않는다.
|
||||
6. 이미 게시된 버전보다 높은 annotated 태그 `v1.2.0`을 생성해 push한다. `npm run release:tag -- --dry-run`으로 검증한 뒤 `npm run release:tag`(GPG 사용 시 `-- --sign`)와 `git push origin v1.2.0`를 실행한다. 태그는 불변이며 게이트를 시작하는 후속 단계지 검증을 대체하지 않는다. 이미 게시된 버전을 재게시하지 않는다: canonical publisher는 버전별 자산이 다른 바이트를 가지면 fail-closed로 중단한다.
|
||||
7. GitLab에서 package-windows, package-macos, publish-release와 의도한 mobile job 상태를 모두 확인한다. publish-release가 Forgejo와 GitLab 양쪽에 게시했는지 로그로 확인한다. pending/stuck/skipped를 GREEN으로 기록하지 않는다.
|
||||
8. Forgejo Release note/asset, `latest.yml`, `update-policy.json`, installer hash를 외부 public URL에서 다시 검증한다.
|
||||
9. 이전 설치본에서 자동 업데이트 E2E를 실행하고 실행 중 버전·프로세스·사용자 데이터 보존을 확인한다.
|
||||
|
|
@ -122,7 +126,7 @@ https://gitlab.twentyoz.kr:8443/api/v4/projects/1172/packages/generic/d3ro-voice
|
|||
|
||||
Desktop release를 게시해도 Android production 출시가 자동으로 완료되지 않는다. Android는 다음을 별도로 증명한다.
|
||||
|
||||
- 준비된 local upload/evidence key와 AdMob identity를 protected CI secret에 주입하고, 사용자 승인 후 생성한 production Firebase config와 함께 version `1.1.0`, versionCode `1010001` AAB 생성
|
||||
- 준비된 local upload/evidence key와 AdMob identity를 protected CI secret에 주입하고, 사용자 승인 후 생성한 production Firebase config와 함께 version `1.2.0`, versionCode `1020001` AAB 생성
|
||||
- package/config/upload signer/ABI/16 KB page size/signed provenance GREEN
|
||||
- public APK 게시 없이 Play internal track에 제한 업로드
|
||||
- Play-signed 실기기 E2E, 12명·연속 14일 closed test, production access 승인
|
||||
|
|
|
|||
|
|
@ -39,15 +39,16 @@
|
|||
| 구성 | 위치 | 상태 |
|
||||
|---|---|---|
|
||||
| 업데이터 서비스 | `apps/desktop/src/main/services/UpdateService.ts` | 단일 feed, 4h 주기, 동의 다이얼로그, skip version |
|
||||
| feed SSOT | `apps/desktop/src/main/update-feed.ts` | GitLab project 1172 `latest` (버전 비고정) |
|
||||
| feed SSOT | `apps/desktop/src/main/update-feed.ts` | canonical Forgejo registry `latest` (버전 비고정) + legacy GitLab mirror 상수 |
|
||||
| 빌더 publish | `apps/desktop/electron-builder.yml` | `provider: generic`, `detectUpdateChannel: false` |
|
||||
| 버전 SSOT | `release/product-version.json` | `1.1.0` / `1010001` |
|
||||
| 버전 SSOT | `release/product-version.json` | `1.2.0` / `1020001` |
|
||||
| 버전 동기화 | `scripts/ci/sync-version.mjs` | 태그·패키지·lockfile·Android/iOS/.NET 일치 fail-closed |
|
||||
| 메타데이터 검증 | `scripts/ci/verify-release-metadata.mjs` | feed·publisher·workflow 계약 self-test |
|
||||
| GitLab publisher | `scripts/ci/publish-gitlab-release.mjs` | 버전별 + `latest`, 설치자산 우선, public 재검증 |
|
||||
| GitLab publisher | `scripts/ci/publish-gitlab-release.mjs` | 버전별 + `latest`, 설치자산 우선, public 재검증 (legacy mirror) |
|
||||
| Forgejo publisher | `scripts/ci/publish-forgejo-release.mjs` | canonical feed·Release·policy 게시, 동일 버전 재게시 fail-closed |
|
||||
| Windows 산출물 gate | `scripts/ci/verify-windows-release-artifact.ps1` | Authenticode·PE version·SHA-512 |
|
||||
| CI | `.gitlab-ci.yml` `package-*` → `publish-release` | 태그 전용 |
|
||||
| Forgejo | `.forgejo/workflows/*` | **사이트 배포만**. 릴리스 배포 금지됨 |
|
||||
| Forgejo | `.forgejo/workflows/release.yml` | tag 전용 release·feed 게시 (동일 publisher 수렴) |
|
||||
| 릴리스 노트 | `CHANGELOG.md` (Keep a Changelog) | publisher가 섹션 누락 시 실패 |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> Status: ACTIVE
|
||||
> Last full audit: 2026-09-13
|
||||
> Scope: entire monorepo `D:/workspace/D3ROVoice` at product version `1.1.0`
|
||||
> Scope: entire monorepo `D:/workspace/D3ROVoice` at product version `1.2.0`
|
||||
> Purpose: let any agent (or human) answer two questions in under a minute:
|
||||
> 1. **What infrastructure exists?** (build, CI, services, APIs, data, packages, deploy)
|
||||
> 2. **How far is each feature developed?** (per surface, with file anchors and status)
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ Full detail: [`09-supabase-backend.md`](./09-supabase-backend.md).
|
|||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `release/product-version.json` | version `1.1.0`, `androidVersionCode`/`iosBuildNumber` `1010001`, releaseDate, desktop license keyId |
|
||||
| `release/product-version.json` | version `1.2.0`, `androidVersionCode`/`iosBuildNumber` `1020001`, releaseDate, desktop license keyId |
|
||||
| `release/android-release-identity.json` | package `com.d3ro.voice`, Play app ID, app-signing SHA-256, upload cert SHA-256, evidence keyId, AdMob unit IDs |
|
||||
| `release/desktop-license-public.pem` | Ed25519 public key for desktop offline licenses |
|
||||
| `release/mobile-release-evidence-public.pem` | Ed25519 public key for mobile release evidence |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
> Surface: `packages/*` (npm workspaces)
|
||||
> Source of truth for: shared domain logic, design systems, i18n, API client
|
||||
|
||||
All packages are private, version `1.1.0`, source-only (`main`/`types` point at `src`).
|
||||
All packages are private, version `1.2.0`, source-only (`main`/`types` point at `src`).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]`
|
|||
| INFRA-11 | Docker + NAS deploy | [x] | `docker-compose.nas.yml`, `scripts/deploy-nas.*` |
|
||||
| INFRA-12 | Cloudflare edge + tunnel | [x] | `server/cloudflare-worker`, Cloudflare Tunnel `kd-nas` |
|
||||
| INFRA-13 | Site deploy (Cloudflare Pages + GitHub Pages) | [x] | `.forgejo/workflows/deploy-site.yml`, `.github/workflows/deploy-site.yml` |
|
||||
| INFRA-15 | Update & release system | [x] | Canonical Forgejo feed + channels/policy (`release/update-policy.json`, `src/main/update-policy.ts`), canonical publisher `scripts/ci/publish-forgejo-release.mjs`, legacy GitLab mirror; `npm run release:metadata:test`. v1.1.0 published to Forgejo & official download centers active on web (`/download`, `/releases`) and site (`#download`). |
|
||||
| INFRA-15 | Update & release system | [x] | Canonical Forgejo feed + channels/policy (`release/update-policy.json`, `src/main/update-policy.ts`), canonical publisher `scripts/ci/publish-forgejo-release.mjs`, legacy GitLab mirror; `npm run release:metadata:test`. v1.1.0 was published to Forgejo on 2026-09-15; product version moved to `1.2.0` as a forward-fix with CI-only publication, a same-version re-release guard, and download centers that link the feed instead of repository paths. |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res
|
|||
| ID | Area | Gap | Evidence | Suggested next step |
|
||||
|---|---|---|---|---|
|
||||
| GAP-QA-01 | Quality | Extreme Red Team: headful end-to-end bug hunting across real desktop Electron, Web Next.js, and CI pipelines. | `red_team_log.md`, `tests/e2e/red_team_cycle*.spec.ts`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[x]` 2026-09-15: 18 scenarios executed, 14 defects caught and 100% resolved (infinite chunking loop DEF-008, IPC signature mismatch DEF-004, markdown editor typing rollback DEF-006, Web RSC Link serialization DEF-012, secret scanner lookahead DEF-013, etc.). All 18 scenarios GREEN with zero regressions. |
|
||||
| GAP-REL-01 | Release | Official v1.1.0 release publication to Forgejo and active public download center deployment. | `scripts/ci/publish-forgejo-release.mjs`, `apps/web/src/app/download/page.tsx`, `site/src/sections/Download.tsx`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[x]` 2026-09-15: v1.1.0 release assets (`D3RO-Voice-Setup-1.1.0-x64.exe`, `.blockmap`, `latest.yml`, `update-policy.json`) published to canonical Forgejo registry and release hub. Public download centers in `apps/web` (`/download`, `/releases`) and `site` (`#download`) activated with direct 1.1.0 installer download, SHA-256 verification, and mirror links. Playwright E2E tests verified GREEN. |
|
||||
| GAP-REL-01 | Release | Official release publication to Forgejo and active public download center deployment. | `scripts/ci/publish-forgejo-release.mjs`, `apps/web/src/app/download/page.tsx`, `site/src/sections/Download.tsx`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[~]` 2026-09-15: v1.1.0 release assets (`D3RO-Voice-Setup-1.1.0-x64.exe`, `.blockmap`, `latest.yml`, `update-policy.json`) published to canonical Forgejo registry and release hub. 2026-09-16: the published 1.1.0 installer carries no Authenticode signature, so it does not satisfy the release policy; product version moved to `1.2.0` and publication must come from CI with the signing gate GREEN. Download centers in `apps/web` (`/download`) and `site` (`#download`) link the canonical Forgejo feed. |
|
||||
| GAP-REL-02 | Release | Windows stable publication needs an external public-trust Authenticode PFX, its password, the exact signer subject, and a Forgejo token, none of which live in the repository. | `.forgejo/workflows/release.yml`, `.gitlab-ci.yml`, `scripts/ci/verify-windows-release-artifact.ps1` | `[!]` 2026-09-16: every publisher fails closed without `WIN_CSC_*` and `FORGEJO_TOKEN`; provide them as protected CI secrets, then re-run the `1.2.0` tag pipeline. |
|
||||
| GAP-ADS-01 | Ads | 9 of 10 desktop ad adapters still extend `UnavailableAdAdapter` (`provider_not_integrated`). | `apps/desktop/src/main/services/ads/*` | `[~]` 2026-09-13: `DirectHouseSponsorAdapter` is now a real configurable REST adapter (bid/impression/click/reward via `endpointUrl`; fail-closed when unconfigured; 22 unit tests GREEN). Remaining 9 need official SDKs/authenticated endpoints. |
|
||||
| GAP-ADS-02 | Ads | Desktop mediation reward accounting is not wired to license quota (`claimReward` still returns no tokens). | `AdMediationEngine.ts`, `AppLayout.tsx` | Wire verified `reportRewardCompletion` to `LicenseService` quota after the direct sponsor endpoint exists. |
|
||||
| GAP-ID-01 | Identity | Supabase, .NET JWT/SQLite, and the desktop offline license each had their own tier/role shape. | `@d3ro/core/entitlement`, `LicenseService`, `entitlement-context` | `[~]` 2026-09-13: canonical `EntitlementSnapshot` + `resolveEntitlement` added with tests; desktop tier normalization + `isPro` fixed. Full adoption tracked as GAP-ID-02. |
|
||||
|
|
|
|||
|
|
@ -2,17 +2,22 @@
|
|||
|
||||
> 상태: ACTIVE
|
||||
> 최초 감사 기준: 2026-08-21 / `main` @ `a9c9a1c`
|
||||
> 마지막 release-readiness 스냅샷: 2026-08-29 (Play Console live 재확인, release identity `1.1.0`/`1010001`, production AAB 대기)
|
||||
> 마지막 release-readiness 스냅샷: 2026-09-16 (release identity `1.2.0`/`1020001`, production AAB 대기)
|
||||
> 직전 스냅샷: 2026-08-29 (Play Console live 재확인, 당시 identity `1.1.0`/`1010001`)
|
||||
> 제품 모바일 런타임: `apps/mobile-rn`
|
||||
> 데이터·권한 정본: `server/supabase/migrations` + Supabase Auth/Storage/Edge Functions
|
||||
> 제품화 재개 핸드오프: [`MOBILE_PRODUCTIZATION_HANDOFF_2026-08-21.md`](./MOBILE_PRODUCTIZATION_HANDOFF_2026-08-21.md)
|
||||
> 이 문서는 이전 `docs/v3/00-mobile-master-plan.md`의 미래 로드맵을 대체하는 구현·검증 정본이다.
|
||||
|
||||
## 0. 2026-08-29 출시 게이트 스냅샷
|
||||
## 0. 2026-09-16 출시 게이트 스냅샷
|
||||
|
||||
데스크톱 `1.1.0`은 2026-09-15에 게시되었고, 그 뒤 커밋된 기능(사전 import/export, push
|
||||
전송, entitlement gate, release pipeline 정비)을 담는 forward-fix로 제품 버전이
|
||||
`1.2.0`/`1020001`로 올라갔다. 아래 표는 그 identity를 기준으로 한다.
|
||||
|
||||
| 게이트 | 현재 증거 | 판정 |
|
||||
|---|---|---|
|
||||
| release identity | `release/product-version.json`: version `1.1.0`, Android versionCode / iOS build `1010001` | source SSOT GREEN; 실제 store artifact 미검증 |
|
||||
| release identity | `release/product-version.json`: version `1.2.0`, Android versionCode / iOS build `1020001` | source SSOT GREEN; 실제 store artifact 미검증 |
|
||||
| Play 앱 | app ID `4976102237698469110`, package `com.d3ro.voice`, 상태 `임시` | 앱 생성 GREEN, 제출 RED |
|
||||
| Play 설정 | 0/11, 첫 AAB 0건 | RED |
|
||||
| App Signing | Play SHA-256 `01:00:19:21:DB:4F:33:40:85:CE:21:E4:B8:DE:CC:BD:71:DA:87:67:C5:6E:3B:59:83:2A:A1:C8:29:EA:0D:AB` | Play identity 확인; upload certificate 대조는 첫 AAB 후 |
|
||||
|
|
@ -343,7 +348,7 @@
|
|||
- [!] EXT-005 Google service account, Android Publisher API, RTDN Pub/Sub
|
||||
- [~] EXT-006 AdMob app `ca-app-pub-1039714767792854~6427959892`, banner `/9840591290`, rewarded `/2255790918`, SSV 설정 — identity/URL SSOT는 확정. `검토 필요`·`광고 게재 제한`·결제 프로필·store 미연결·test-device 실제 지급 E2E 대기
|
||||
- [x] EXT-007 개인정보 처리방침·이용약관·계정 삭제 공개 URL — NAS host/runtime에 동일 해시로 좁게 배포, `/privacy/`, `/terms/`, `/delete-account/` 외부 HTTPS 200·정확한 title·삭제 요청 링크 확인. 모바일 Settings와 Paywall에서 canonical URL 진입
|
||||
- [!] EXT-008 local upload key·Ed25519 evidence keypair는 ACL/readback/roundtrip GREEN. production Firebase/AdMob/signing/evidence secret의 CI 보관·보호 환경 승인·복구 백업 및 `1.1.0`/`1010001` production AAB workflow는 대기
|
||||
- [!] EXT-008 local upload key·Ed25519 evidence keypair는 ACL/readback/roundtrip GREEN. production Firebase/AdMob/signing/evidence secret의 CI 보관·보호 환경 승인·복구 백업 및 `1.2.0`/`1020001` production AAB workflow는 대기
|
||||
- [!] EXT-011 Firebase Console `u/0`, `u/1` 모두 D3RO project 0개 — 사용자 action-time 승인 후 production project·Android app·Play fingerprint·FCM 생성/연결
|
||||
- [!] EXT-009 Fold6 physical-device install/share/OAuth/purchase 증거 — exact `.11` APK Taildrop 전송 완료, 사용자 설치·실행·공유/OAuth/구매 확인 필요
|
||||
- [!] EXT-010 Play closed test 12명·연속 14일 완료와 production access 승인 — 2026-08-29 현재 0명, access disabled
|
||||
|
|
@ -361,7 +366,7 @@
|
|||
| Latest Android E2E test artifact | JDK 17 `:app:assembleE2e -PreactNativeArchitectures=arm64-v8a,x86_64` + artifact/build-config verifiers | BUILD/verifier GREEN; `com.d3ro.voice`, version `0.0.0-e2e.20260821.11`/`202608221`, non-debuggable, embedded bundle 3,808,068 B·Whisper model 77,691,713 B/SHA 검증, Google test AdMob ID, arm64+x86_64. APK 146,739,935 B, SHA-256 `74035B69DB0A564846DF5ED4B85CA5D0E2289D5BB7D73DEE02C35DFD57380C2B`. API 34 exact-APK fresh install와 cold launch GREEN. production artifact가 아니다 | `apps/mobile-rn/android/app/build/outputs/apk/e2e/app-e2e.apk`, `scripts/ci/verify-android-artifact.mjs`, `scripts/ci/verify-mobile-build-config.mjs` | 2026-08-21 |
|
||||
| Public Forgejo distribution | 최신 E2E TEST-DEMO 고유 asset upload + anonymous redownload hash 대조 | PENDING; `.11` SHA-256 `74035B69…380C2B` 공개 업로드·anonymous redownload 검증은 수행하지 않음 | Forgejo release asset | 2026-08-21 |
|
||||
| Mobile release boundary | `npm run release:mobile:boundary:test` + source-contract/security/syntax/YAML checks | GREEN; release mode·expected version/package/cert/prod AdMob·Ed25519 signed evidence와 artifact hash 일치, immutable snapshot, hardlink/reparse/path-swap/static APK 우회·overwrite·test AdMob/debug signer/nonrelease 거부를 검증. 실제 production APK/AAB에는 아직 실행하지 않음 | `scripts/ci/mobile-release-evidence-lib.mjs`, `scripts/ci/verify-mobile-release-boundary.mjs`, `.github/workflows/release.yml` | 2026-08-21 |
|
||||
| Production Android release gate | release/config/artifact/provenance self-tests + Play/Firebase/AdMob live 재확인 | release identity `1.1.0`/`1010001`, Play app ID `4976102237698469110`, package `com.d3ro.voice`, app-signing SHA-256, local upload/evidence key, AdMob identity를 확인했다. Play는 `임시`, 설정 0/11, AAB 0건, closed tester 0/12명, production access disabled다. Firebase project·CI secret 주입·AAB가 없으므로 현재 판정은 RED다 | `release/product-version.json`, `release/android-release-identity.json`, `scripts/ci/verify-mobile-release-config.mjs`, `scripts/ci/verify-android-artifact.mjs`, live consoles | 2026-08-29 |
|
||||
| Production Android release gate | release/config/artifact/provenance self-tests + Play/Firebase/AdMob live 재확인 | release identity `1.2.0`/`1020001`, Play app ID `4976102237698469110`, package `com.d3ro.voice`, app-signing SHA-256, local upload/evidence key, AdMob identity를 확인했다. Play는 `임시`, 설정 0/11, AAB 0건, closed tester 0/12명, production access disabled다. Firebase project·CI secret 주입·AAB가 없으므로 현재 판정은 RED다 | `release/product-version.json`, `release/android-release-identity.json`, `scripts/ci/verify-mobile-release-config.mjs`, `scripts/ci/verify-android-artifact.mjs`, live consoles | 2026-08-29 |
|
||||
| App Links release identity | source/live exact certificate 대조 | `site/public`, `apps/web/public`, `apps/api-server/wwwroot`의 source 3개는 Play app-signing SHA-256으로 갱신. live는 아직 폐기된 과거 certificate를 반환하므로 deploy·public 재검증 전까지 RED | `*/.well-known/assetlinks.json`, `scripts/ci/verify-android-app-links.mjs`, live `d3ro.chanpaca.net` | 2026-08-29 |
|
||||
| Public legal and deletion resources | scoped NAS host/container deploy + anonymous HTTPS render | 개인정보처리방침, 이용약관, 앱 외부 계정 삭제 요청 경로를 기존 사이트 전체와 분리해 배포했다. host와 running container 4개 파일 SHA 일치; `/privacy/`, `/terms/`, `/delete-account/` 모두 외부 HTTPS 200·정확한 title, privacy→deletion 링크 visible. 실서버 본문 운영자는 `YUN CHAN`, Cloudflare email-protection 복호화 연락처는 `yunchan8804@gmail.com`, `TWENTYOZ` 잔존은 0건이다. Google Play 정책상 필요한 앱 식별·개발자 문의·수집/공유·보관/삭제·외부 삭제 요청 경로를 포함하고 모바일 Settings/Paywall에도 canonical link를 노출 | `site/public/legal.css`, `site/public/privacy/index.html`, `site/public/terms/index.html`, `site/public/delete-account/index.html`, `apps/mobile-rn/src/screens/SettingsScreen.tsx`, live `d3ro.chanpaca.net` | 2026-08-21 |
|
||||
| Android upload signing identity | create-only 3072-bit RSA PKCS12 + Windows credential vault + certificate readback | 2026-08-21에 생성한 local upload-key 후보 SHA-256은 `4F:AC:69:24:82:1C:50:DA:AB:ED:76:49:32:A5:3C:48:6F:8C:6C:5F:34:B9:F1:8D:B9:20:AA:40:99:15:2B:54`다. 다만 production CI secret·오프라인 복구 백업·실제 AAB signer 증거는 없다. Play Console upload certificate는 첫 AAB 업로드 후 표시되며, 현재 확인한 Play app-signing SHA-256과 분리해 대조해야 한다 | `release/android-release-identity.json`, `scripts/gen-keystore.js`, Windows Credential Manager | 2026-08-29 |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Google Play 출시 체크리스트
|
||||
|
||||
기준일: 2026-08-29. 체크 표시는 이 문서를 읽은 사람이 실제 증거를 확인한 뒤에만 바꾼다. 소스 검사, debug/E2E APK, HTTP 200 하나만으로 release GREEN을 선언하지 않는다.
|
||||
기준일: 2026-09-16. 체크 표시는 이 문서를 읽은 사람이 실제 증거를 확인한 뒤에만 바꾼다. 소스 검사, debug/E2E APK, HTTP 200 하나만으로 release GREEN을 선언하지 않는다.
|
||||
|
||||
### 2026-08-29 Play Console live 스냅샷
|
||||
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| 앱 | `D3RO Voice`, package `com.d3ro.voice`, app ID `4976102237698469110` | 생성 완료 |
|
||||
| Console 상태 | `임시`; 첫 AAB 업로드 0건, 설정 0/11 | 제출 불가 |
|
||||
| release identity | versionName `1.1.0`, Android versionCode `1010001` | 소스 SSOT에서 확정; AAB 증거는 아직 없음 |
|
||||
| release identity | versionName `1.2.0`, Android versionCode `1020001` | 소스 SSOT에서 확정; AAB 증거는 아직 없음 |
|
||||
| Play app-signing SHA-256 | `01:00:19:21:DB:4F:33:40:85:CE:21:E4:B8:DE:CC:BD:71:DA:87:67:C5:6E:3B:59:83:2A:A1:C8:29:EA:0D:AB` | Console 실확인 |
|
||||
| upload key | 외부 PKCS12·user-only ACL·Credential Manager·private-key readback GREEN; cert SHA-256 `4F:AC:69:24:82:1C:50:DA:AB:ED:76:49:32:A5:3C:48:6F:8C:6C:5F:34:B9:F1:8D:B9:20:AA:40:99:15:2B:54` | local key 준비. Console 표시·AAB signer 대조는 첫 AAB 후 |
|
||||
| release evidence | Ed25519 keypair roundtrip GREEN; public key·keyId `2797d3e63affd2348941bc2871b57e520c958f7f5da1a4bbe8aa46904a890b7f` SSOT | CI private-key secret·복구 백업 미완료 |
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
- [x] package는 `com.d3ro.voice`: `apps/mobile-rn/android/app/build.gradle:289-302`.
|
||||
- [x] release cleartext traffic은 false이고 release ABI는 arm64-v8a: `build.gradle:372-386`.
|
||||
- [x] release gate는 keystore, version, Firebase config, production AdMob app/banner/rewarded ID를 요구한다: `build.gradle:45-103,157-219`.
|
||||
- [x] release identity를 `D3RO_VERSION_NAME=1.1.0`, `D3RO_VERSION_CODE=1010001`로 확정했다.
|
||||
- [x] release identity를 `D3RO_VERSION_NAME=1.2.0`, `D3RO_VERSION_CODE=1020001`로 확정했다.
|
||||
- [x] Ed25519 release-evidence keypair을 생성하고 private key를 저장소 밖 user-only ACL 경로에, public key를 `release/mobile-release-evidence-public.pem`에 보존했다. keyId `2797d3e63affd2348941bc2871b57e520c958f7f5da1a4bbe8aa46904a890b7f`, sign/verify roundtrip GREEN이다.
|
||||
- [ ] upload/evidence private key·Firebase·AdMob secrets를 승인된 CI secret store에 주입하고 별도 복구 백업을 검증했다.
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ AdMob 콘솔 준비를 실제 광고 게재 승인·수입·Play release 완료
|
|||
## 13. Production 제출·점진 배포
|
||||
|
||||
- [ ] internal/closed에서 검증한 **동일 AAB SHA** 또는 정당한 수정 후 새로 전부 검증한 AAB를 사용했다.
|
||||
- [x] versionCode `1010001`용 release notes ko/en을 작성했다: `apps/mobile-rn/metadata/android/{ko-KR,en-US}/changelogs/1010001.txt`.
|
||||
- [x] versionCode `1020001`용 release notes ko/en을 작성했다: `apps/mobile-rn/metadata/android/{ko-KR,en-US}/changelogs/1020001.txt`.
|
||||
- [ ] country/region과 가격·세금·정책 적용 범위를 검토했다.
|
||||
- [ ] device catalog 제외가 기능/SDK 사실에 근거하며 불필요하게 넓지 않다.
|
||||
- [ ] 모든 Console warning과 policy declaration을 검토했다.
|
||||
|
|
@ -331,7 +331,7 @@ AdMob 콘솔 준비를 실제 광고 게재 승인·수입·Play release 완료
|
|||
| Placeholder | 실제 값 소유 위치 | 완료 조건 |
|
||||
| -------------------------------------------- | ---------------------------- | ------------------------------------------------------- |
|
||||
| `<PLACEHOLDER_RELEASE_COMMIT_SHA>` | Git remote/CI | 권위 있는 release source SHA |
|
||||
| versionName `1.1.0` / versionCode `1010001` | release SSOT | 확정. 실제 AAB·Console metadata 대조는 대기 |
|
||||
| versionName `1.2.0` / versionCode `1020001` | release SSOT | 확정. 실제 AAB·Console metadata 대조는 대기 |
|
||||
| `<PLACEHOLDER_RELEASE_AAB_SHA256>` | restricted artifact/evidence | 실제 production AAB hash |
|
||||
| upload SHA-256 `4F:AC:69:24:...:15:2B:54` | external key/Play Console | local 값 확정. AAB signer·Console 표시와 일치 대기 |
|
||||
| evidence keyId `2797d3e6...4a890b7f` | release identity/CI secret | public·roundtrip 확인. private CI secret·복구 백업 대기 |
|
||||
|
|
|
|||
22
package-lock.json
generated
22
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "d3ro-voice-monorepo",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "d3ro-voice-monorepo",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"apps/desktop",
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
"apps/admin": {
|
||||
"name": "@d3ro/admin",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@d3ro/api-client": "*",
|
||||
"@d3ro/core": "*",
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
},
|
||||
"apps/desktop": {
|
||||
"name": "@d3ro/desktop",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@d3ro/core": "*",
|
||||
|
|
@ -156,7 +156,7 @@
|
|||
},
|
||||
"apps/mobile-rn": {
|
||||
"name": "@d3ro/mobile-rn",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"extraneous": true,
|
||||
"dependencies": {
|
||||
"@d3ro/api-client": "file:../../packages/api-client",
|
||||
|
|
@ -209,7 +209,7 @@
|
|||
},
|
||||
"apps/web": {
|
||||
"name": "@d3ro/web",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@d3ro/api-client": "*",
|
||||
"@d3ro/core": "*",
|
||||
|
|
@ -20397,7 +20397,7 @@
|
|||
},
|
||||
"packages/api-client": {
|
||||
"name": "@d3ro/api-client",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@d3ro/core": "*",
|
||||
|
|
@ -20414,7 +20414,7 @@
|
|||
},
|
||||
"packages/core": {
|
||||
"name": "@d3ro/core",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"docx": "^9.6.1"
|
||||
|
|
@ -20425,7 +20425,7 @@
|
|||
},
|
||||
"packages/i18n": {
|
||||
"name": "@d3ro/i18n",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
|
|
@ -20436,7 +20436,7 @@
|
|||
},
|
||||
"packages/ui": {
|
||||
"name": "@d3ro/ui",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@d3ro/core": "*"
|
||||
|
|
@ -20456,7 +20456,7 @@
|
|||
},
|
||||
"packages/ui-native": {
|
||||
"name": "@d3ro/ui-native",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/react": "*"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "d3ro-voice-monorepo",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice — 멀티플랫폼 AI 음성 어시스턴트 (Monorepo)",
|
||||
"author": "D3RO",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/api-client",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice API 클라이언트 — Supabase 래퍼 (web/desktop/mobile 공유)",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/core",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 공유 비즈니스 로직 — 타입, 에러, IPC 채널, 상수, 유틸",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/i18n",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 공유 i18n — 12개 locale, 타입 안전 키, Context, 포맷 유틸",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/ui-native",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice React Native DS — MetalCard/PhosphorText/Led/WaveBars etc.",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/ui",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 공유 UI — 디자인 시스템 컴포넌트 + 테마 토큰 + CSS 변수 맵",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"version": "1.1.0",
|
||||
"androidVersionCode": 1010001,
|
||||
"iosBuildNumber": 1010001,
|
||||
"releaseDate": "2026-08-29",
|
||||
"version": "1.2.0",
|
||||
"androidVersionCode": 1020001,
|
||||
"iosBuildNumber": 1020001,
|
||||
"releaseDate": "2026-09-16",
|
||||
"desktopLicensePublicKeyId": "5c52b765135ee2531c681b53cd4dc0e96fff8737f496c0b04ba8334fc81a887f"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ if (dryRun) {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
// 0) 이미 게시된 버전은 재게시하지 않는다 (SemVer 동일 버전 재릴리스 금지).
|
||||
// 버전별 경로에 같은 크기의 자산이 있으면 재시도/재실행으로 보고 통과시키고,
|
||||
// 다른 바이트를 가진 자산이 있으면 fail-closed로 중단한다.
|
||||
await assertVersionNotRepublished(sorted);
|
||||
|
||||
// 1) 버전별(immutable) 패키지 업로드
|
||||
for (const file of sorted) {
|
||||
await uploadToRegistry(file, `${packageVersionedUrl}/${encodeURIComponent(safeAssetName(file.name))}`, {
|
||||
|
|
@ -188,6 +193,24 @@ async function sha256(path) {
|
|||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
async function assertVersionNotRepublished(localFiles) {
|
||||
for (const file of localFiles) {
|
||||
const url = `${packageVersionedUrl}/${encodeURIComponent(safeAssetName(file.name))}`;
|
||||
const head = await forgejoFetch(url, { method: "HEAD" }).catch(() => null);
|
||||
if (!head || !head.ok) continue;
|
||||
|
||||
const remoteLength = Number(head.headers.get("content-length"));
|
||||
const localLength = (await stat(file.path)).size;
|
||||
if (!Number.isFinite(remoteLength) || remoteLength === localLength) continue;
|
||||
|
||||
throw new Error(
|
||||
`${tag} is already published with different bytes (${safeAssetName(file.name)}: ` +
|
||||
`remote ${remoteLength} bytes, local ${localLength} bytes). Releases are immutable — ` +
|
||||
"lift the version and publish a new tag instead of re-publishing this one.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadToRegistry(file, url, { replace = false, immutable = false } = {}) {
|
||||
const fileStat = await stat(file.path).catch(() => null);
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,10 @@ function validate(surfaces) {
|
|||
fail(surfaces.forgejoPublisher.includes('verifyPublicFile'), 'forgejo_publisher_public_verification_missing')
|
||||
fail(surfaces.forgejoPublisher.includes('update-policy.json'), 'forgejo_publisher_policy_upload_missing')
|
||||
fail(surfaces.forgejoPublisher.includes('CHANGELOG.md'), 'forgejo_publisher_changelog_gate_missing')
|
||||
fail(
|
||||
surfaces.forgejoPublisher.includes('assertVersionNotRepublished'),
|
||||
'forgejo_publisher_rerelease_guard_missing',
|
||||
)
|
||||
fail(
|
||||
surfaces.forgejoPublisher.includes('/api/packages/') &&
|
||||
surfaces.forgejoPublisher.includes('generic'),
|
||||
|
|
@ -309,6 +313,13 @@ if (process.argv.includes('--self-test')) {
|
|||
},
|
||||
'forgejo_publisher_asset_first_order_missing',
|
||||
)
|
||||
expectRejected(
|
||||
surfaces,
|
||||
(candidate) => {
|
||||
candidate.forgejoPublisher = candidate.forgejoPublisher.replaceAll('assertVersionNotRepublished', 'uploadAnyway')
|
||||
},
|
||||
'forgejo_publisher_rerelease_guard_missing',
|
||||
)
|
||||
expectRejected(
|
||||
surfaces,
|
||||
(candidate) => {
|
||||
|
|
@ -373,7 +384,7 @@ if (process.argv.includes('--self-test')) {
|
|||
if (!missingDesktopKeyRejected) {
|
||||
throw new Error('release_metadata_self_test_failed:desktop_license_public_key_missing')
|
||||
}
|
||||
result.negativeCases = 13
|
||||
result.negativeCases = 14
|
||||
}
|
||||
|
||||
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
||||
|
|
|
|||
4
site/package-lock.json
generated
4
site/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "d3ro-voice-site",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "d3ro-voice-site",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "d3ro-voice-site",
|
||||
"private": true,
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 5199 --host",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
// NAS 배포는 바이너리를 포함하지 않으므로 `/releases/...` 같은 로컬 경로는
|
||||
// 실제 배포 환경에서 404가 된다.
|
||||
|
||||
export const DESKTOP_VERSION = '1.1.0'
|
||||
export const DESKTOP_VERSION = '1.2.0'
|
||||
|
||||
/** 릴리스 게시일. `release/product-version.json`의 releaseDate와 같아야 한다. */
|
||||
export const DESKTOP_RELEASE_DATE = '2026-09-16'
|
||||
|
||||
const FORGEJO_ORIGIN = 'https://git.chanpaca.net'
|
||||
const FORGEJO_OWNER = 'yunchan'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue