docs: record 1.2.0 and the state of the published 1.1.0
Some checks failed
deploy-site / deploy (push) Failing after 54s
release / release-windows (push) Failing after 2m56s

The changelog still described unreleased work under 1.1.0, which was already
published with its own notes. Those notes are restored verbatim for history,
and the new work has its own 1.2.0 section that the feed publisher will turn
into release notes.

The release guide, infrastructure map, and mobile SSOT now carry the 1.2.0
identity, state that installer binaries are distributed through the feed and
never committed, and record that the published 1.1.0 installer is unsigned and
is being superseded rather than rewritten. Backlog entries cover the remaining
external signing and token secrets.
This commit is contained in:
Yun Chan 2026-09-16 23:50:01 +09:00
parent 035d0a76f5
commit 359b244dc9
10 changed files with 77 additions and 58 deletions

View file

@ -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 승인

View file

@ -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가 섹션 누락 시 실패 |
---