docs: record Wave 3 surface consolidation and fold NAS-only whisper into compose
- docs/REFACTOR_WAVE3_REPORT.md and the Wave 3 policy: canonical map, production changes, verification and remaining external steps. - Gap backlog: GAP-BILL-01 resolved; new GAP-BILL-02 (Payple renewal never ran, Payple client key never set), GAP-WEB-01 (tunnel host for /app), GAP-OPS-01 (NAS compose/.env drift), GAP-CI-01, GAP-I18N-02, GAP-TEAM-02. - design.md: hero loop decision (numbers taken from the app capsule), pricing mismatch closed; feature catalog SHELL-11 updated. - docs/map, release guide and mobile release docs no longer describe the deleted wwwroot, binaries, Dockerfile.admin, NAS site copy or .github CI. - docker-compose.nas.yml gains the d3ro-whisper service that only existed in the NAS copy, so the repository file is the complete definition. - refactor-wave skill: Wave 3 index and lessons P10-P12.
This commit is contained in:
parent
92978607da
commit
e87ce63440
17 changed files with 187 additions and 46 deletions
|
|
@ -103,3 +103,22 @@ D3RO는 이미 3계층 강제 규칙 체계를 가진다. 본 정책은 이를 *
|
|||
| DP7 DRY 임계치 | 3회 | 2026-07-22 |
|
||||
| DP9 데드코드 강도 | 공격적 DELETE (외부 API 노출 export는 grep 후 KEEP) | 2026-07-22 |
|
||||
| DP6 typecheck 범위 | 전체 모노레포 typecheck 강제 (ui/i18n/core 공유) | 2026-07-22 |
|
||||
|
||||
---
|
||||
|
||||
## Wave 3 (2026-09-26) — 표면·계약 단일화
|
||||
|
||||
사용자 결정: "두 벌씩 만든 것을 전부 하나로." 확정 사항과 정본은 아래와 같다.
|
||||
|
||||
| # | 결정 포인트 | 정본 | 파생(자동·검사) |
|
||||
|---|---|---|---|
|
||||
| W3-1 | 요금 | `packages/core/src/plan-catalog.ts` `PLAN_PRICE_KRW` (Free 0 / Pro 2,900 / Pro+ 8,900, 월). 기존 구독자도 다음 갱신부터 적용 | Deno `_shared/core-contract.generated.ts` (`npm run contract:sync`, CI `contract:check`) |
|
||||
| W3-2 | 공개 URL | `packages/core/src/web-urls.ts` — 사이트 `/`, 웹앱 `/app`(Next basePath), `billingUrl()`, `SITE_URLS` | 같은 생성 파일 |
|
||||
| W3-3 | 결제 진입 | 웹앱 `/app/billing` 하나. 데스크톱·모바일·사이트·Stripe 복귀는 모두 `billingUrl()`. 복귀 쿼리는 `success=1`/`canceled=1` | — |
|
||||
| W3-4 | 웹앱 배포 | NAS Docker(`d3ro_voice_web`) → Cloudflare Tunnel 호스트 → 사이트 브리지 워커가 `/app/*`를 전달 | — |
|
||||
| W3-5 | 랜딩·다운로드·법률·초대·assetlinks | `site/` 한 벌. apps/web·api-server wwwroot 사본 삭제, 웹앱 `/download`는 사이트로 리다이렉트 | — |
|
||||
| W3-6 | 설치 파일 | Forgejo feed만. 저장소에 추적된 바이너리 사본 삭제 | — |
|
||||
| W3-7 | CI | Forgejo(`.forgejo/workflows`) 한 벌. `.github`는 GitHub 원격이 없어 실행된 적이 없으므로 필요한 잡만 옮기고 삭제 | — |
|
||||
| W3-8 | 사이트 배포 | Cloudflare Pages 한 경로. GitHub Pages·NAS wwwroot 복사 경로 삭제 | — |
|
||||
|
||||
DRY 원칙 보강: Deno처럼 정본을 직접 import할 수 없는 런타임은 **생성 사본 + `--check` 게이트**로만 복제를 허용한다(`version:sync` 패턴과 같음). 손으로 옮겨 적은 사본은 금지.
|
||||
|
|
|
|||
63
docs/REFACTOR_WAVE3_REPORT.md
Normal file
63
docs/REFACTOR_WAVE3_REPORT.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Refactor Wave 3 — 표면·계약 단일화 (2026-09-26)
|
||||
|
||||
사용자 지시: "두 벌씩 만든 것을 전부 하나로 합쳐 정리." 정책은 `docs/REFACTOR_POLICY.md` Wave 3 절(W3-1~W3-8).
|
||||
|
||||
## 커밋
|
||||
|
||||
| 커밋 | WS | 내용 |
|
||||
|---|---|---|
|
||||
| `e689683` | 선행 | 랜딩 사이트를 코드로 확인한 사실 기준으로 재작성(접근성·10개 언어) |
|
||||
| `88f24d8` | WS-A | 가격·쿼터·공개 URL 정본 `packages/core` 두 파일 + Deno 생성 사본(`contract:check`) |
|
||||
| `b6fe588` | WS-B | 웹앱 `/app` basePath·standalone·NAS 컨테이너, 모든 결제 진입을 `billingUrl()`로, 브리지 워커 `/app` 라우팅 |
|
||||
| `cd9d199` | WS-C | wwwroot·apps/web·바이너리 중복 삭제(바이너리 19개 ≈ 568 MiB), API 정적 서빙 제거 |
|
||||
| `dc43884` | WS-D | `.github` 삭제, Forgejo로 CI·payple-renew 이식, 사이트 배포 워크플로 하나로 |
|
||||
| `9297860` | 사이트 | 히어로 반복 예시(앱 녹음 캡슐 재현), 404.html, 법률 페이지 색 |
|
||||
| (이 커밋) | 문서 | 보고서, 백로그, NAS compose 드리프트 병합(whisper) |
|
||||
|
||||
## 정본 지도 (전 → 후)
|
||||
|
||||
| 개념 | 전 | 후 |
|
||||
|---|---|---|
|
||||
| 요금 | Payple 9,900/29,900 · admin ₩12,900/₩24,900·$9.9/$19.9 · 사이트 2,900/8,900 | `plan-catalog.ts` 하나(2,900/8,900) → Deno 생성 사본 |
|
||||
| 쿼터 | core `PREMIUM_MODEL_LIMITS` · Deno `quota.ts` · desktop `QUOTA_LIMITS` · 사이트 | `PLAN_QUOTA` 하나에서 파생 |
|
||||
| 결제 URL | `d3ro.dev/billing`(해석 불가) · `d3ro.chanpaca.net/billing`(랜딩으로 폴백) | `billingUrl()` → `/app/billing` |
|
||||
| 랜딩·다운로드·법률·초대·assetlinks | site · api-server wwwroot · apps/web | site 한 벌 |
|
||||
| 설치 파일 | 저장소 3곳 추적 + Forgejo feed | Forgejo feed만 |
|
||||
| CI | `.github`(한 번도 실행 안 됨) · `.forgejo` 2벌 사이트 배포 · GitLab | Forgejo(+ Android 서명 릴리스는 GitLab) |
|
||||
| 사이트 배포 | Cloudflare Pages · GitHub Pages · NAS wwwroot 복사 | Cloudflare Pages |
|
||||
| NAS compose | 운영본(whisper 포함) ≠ 저장소 | 저장소에 whisper 병합(교체는 GAP-OPS-01) |
|
||||
|
||||
## 운영 반영
|
||||
|
||||
- Supabase Edge Functions: `payple-checkout/renew/webhook/manage` v9, `billing-catalog` v5 — 청구 금액 ₩2,900/₩8,900.
|
||||
새 CLI는 `functions/deno.json` 임포트 맵을 자동으로 쓰지 않아 `--import-map supabase/functions/deno.json`이 필요했다.
|
||||
- Cloudflare Pages `d3ro`: 커밋 `9297860` 게시(`release-identity.json` 확인), 없는 경로 404.
|
||||
- NAS: `d3ro_voice_web`(3002) 기동, `/app/login` 200. 공개는 터널 호스트 추가 대기(GAP-WEB-01).
|
||||
|
||||
## 검증
|
||||
|
||||
- `npm run typecheck`(모노레포) EXIT 0 · 변경 파일 eslint errors 0.
|
||||
- `contract:check`·`version:check`·`release:metadata:test`(음성 14)·`release:mobile:boundary:test`·`security:secrets`·assetlinks GREEN.
|
||||
- Deno `_shared/`+`payple-webhook/` 81/81. core vitest 131/131. api-server `dotnet test` 28/28.
|
||||
- 데스크톱 vitest 1520/1522 — 실패 2건은 기록된 환경 의존 건(캡션 타임아웃, 임베딩 서버 부재).
|
||||
**Node 23으로 돌리면 better-sqlite3 ABI 불일치(130/131)로 365건이 가짜 실패한다. Electron 런타임으로 실행해야 한다**:
|
||||
`ELECTRON_RUN_AS_NODE=1 ../../node_modules/electron/dist/electron.exe ../../node_modules/vitest/vitest.mjs run --config vitest.config.ts`.
|
||||
- web·admin 프로덕션 빌드 GREEN, web 이미지 로컬 스모크(`/app`→login, `/app/download`→사이트 308).
|
||||
- 사이트: 10개 언어 × 320/390/1440 가로 넘침 0, axe(wcag2a·aa·21aa·22aa·best-practice) 위반 0, 감소 모션·일시정지 확인.
|
||||
|
||||
## SKIP / 남은 일
|
||||
|
||||
- 외부 설정(사용자): 터널 공개 호스트 + `WEB_APP_ORIGIN`, Supabase Redirect URLs `/app/**`, Forgejo 시크릿 `SUPABASE_URL`·`CRON_SECRET`,
|
||||
Payple 클라이언트 키, Stripe·Play 가격, NAS `.env` 누락 변수 → `docs/map/11-gap-backlog.md` GAP-BILL-02, GAP-WEB-01, GAP-OPS-01.
|
||||
- `packages/i18n`(12개 언어 JSON)과 `site/src/i18n`(10개 언어 TS)은 체계가 다르다. 사이트는 정적 번들 크기·독립 배포 때문에 유지 — 다음 wave 후보.
|
||||
- 티어 타입(`'pro' | 'pro_plus'`) 정의가 core·api-client·Deno에 여러 벌 남아 있다 — 다음 wave 후보.
|
||||
- 캡슐 CSS 값은 사이트가 앱 style.css를 옮겨 적었다(Electron 팝업 CSS 직접 import 불가). `design.md`에 두 곳 동시 수정 규칙을 적었다.
|
||||
|
||||
## 메타 학습
|
||||
|
||||
1. **"안 돌던 것"도 중복이다.** `.github`는 원격이 없어 실행된 적이 없었고, 그 안의 Payple 갱신 cron 때문에 정기 결제가 멈춰 있었다.
|
||||
정리 전에 "이 파일은 실제로 실행되는가"를 원격·러너 기준으로 확인한다.
|
||||
2. **운영 서버의 사본도 중복이다.** NAS compose가 저장소와 달라져 있었다(서비스 추가, 필수 변수 누락). 배포 전 운영본과 diff한다.
|
||||
3. **Windows에서 Python 텍스트 쓰기는 CRLF를 만든다.** `version:check`가 바이트 비교라 깨졌다. `newline=''`로 쓰거나 Edit 도구를 쓴다.
|
||||
비 raw 문자열의 `\\b`는 백스페이스 문자가 될 수 있다 — 정규식 편집은 Edit 도구로.
|
||||
4. **Tailwind 내장 유틸리티와 이름이 같은 사용자 클래스는 무력화된다**(`break-keep`). 프로젝트 전용 이름을 쓴다(`keep-ko`).
|
||||
|
|
@ -45,10 +45,12 @@ authoritative release commit
|
|||
- `publish-forgejo-release.mjs`는 설치 자산을 먼저, `latest.yml`을 마지막에
|
||||
게시하고, 공개 URL에서 재검증한 뒤 Release 자산을 첨부한다.
|
||||
- 설치 바이너리는 저장소에 커밋하지 않는다. 배포 정본은 Forgejo feed이며,
|
||||
`site/public/releases/*/`, `apps/web/public/releases/*/`,
|
||||
`apps/api-server/wwwroot/releases/*/`는 build graph 밖이라 배포 산출물에
|
||||
포함되지 않는다. 사이트·웹 다운로드 센터는 로컬 경로가 아니라 feed URL을
|
||||
링크한다. (역사적 `1.0.0` 자산만 추적 상태로 남아 있다.)
|
||||
`site/public/releases/*/`, `apps/web/public/releases/*/`는 `.gitignore`로
|
||||
막혀 있어 build graph 밖이고 배포 산출물에 포함되지 않는다(추적 상태로 남아
|
||||
있던 19개 설치본 ~568MiB는 2026-09-26 Wave 3, cd9d199에서 삭제됐고 그 뒤로는
|
||||
추적 자산이 없다). `apps/api-server/wwwroot/`는 정적 파일 서빙 자체와 함께
|
||||
통째로 삭제됐다(같은 커밋) — API는 더 이상 다운로드/릴리스 자산을 서빙하지
|
||||
않는다. 사이트·웹 다운로드 센터는 로컬 경로가 아니라 feed URL을 링크한다.
|
||||
|
||||
- `scripts/ci/sync-version.mjs --check --tag v1.3.0`는 태그, `release/product-version.json`, package/lockfile, Android/iOS 버전 면의 일치를 fail-closed로 검증한다.
|
||||
- `scripts/ci/verify-release-metadata.mjs`는 배포 메타데이터와 CI/publisher 계약을 검증한다.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
D:/workspace/D3ROVoice
|
||||
├── apps/
|
||||
│ ├── desktop/ Electron app (npm workspace @d3ro/desktop)
|
||||
│ ├── web/ Next.js console (npm workspace @d3ro/web)
|
||||
│ ├── web/ Next.js console (npm workspace @d3ro/web); basePath `/app`, served at `d3ro.chanpaca.net/app`
|
||||
│ ├── admin/ Next.js back office (npm workspace @d3ro/admin)
|
||||
│ ├── mobile-rn/ React Native product mobile app (NOT an npm workspace)
|
||||
│ ├── api-server/ ASP.NET Core 10 API (D3ROVoice.Api)
|
||||
|
|
@ -25,8 +25,9 @@ D:/workspace/D3ROVoice
|
|||
│ └── api-client/ @d3ro/api-client — Supabase wrapper + shared types
|
||||
├── server/
|
||||
│ ├── supabase/ Supabase project: config.toml, migrations/, functions/, tests/
|
||||
│ └── cloudflare-worker/ Edge gateway (wrangler.toml + src/index.ts)
|
||||
├── site/ Vite landing site (deployed to Cloudflare Pages + GitHub Pages)
|
||||
│ ├── cloudflare-worker/ Edge gateway (wrangler.toml + src/index.ts)
|
||||
│ └── cloudflare-site-bridge/ Site bridge worker: forwards `/app/*` to WEB_APP_ORIGIN, rewrites upstream redirects, else proxies to Pages
|
||||
├── site/ Vite landing site (deployed to Cloudflare Pages; GitHub Pages removed 2026-09-26, Wave 3)
|
||||
├── resources/ icons/ (empty), sox/ (bundled Windows SoX binaries)
|
||||
├── release/ Version identity SSOT + license/evidence public keys
|
||||
├── scripts/ ~145 automation scripts + scripts/ci/ (31) + scripts/lib/
|
||||
|
|
@ -35,11 +36,10 @@ D:/workspace/D3ROVoice
|
|||
├── tests/e2e/, test-results/ Root-level e2e + last-run artifacts
|
||||
├── scratch/ Large local evidence (APKs, screenshots, DBs) — not build input
|
||||
├── supabase/ Empty scaffolding (.branches/, snippets/) — real project is server/supabase
|
||||
├── .github/workflows/ GitHub Actions (6)
|
||||
├── .gitlab-ci.yml GitLab CI (primary desktop/mobile release pipeline)
|
||||
├── .forgejo/workflows/ Forgejo Actions (site deploy to Cloudflare Pages)
|
||||
├── .forgejo/workflows/ Forgejo Actions (CI, site deploy to Cloudflare Pages, payple-renew cron, portable/release)
|
||||
├── docker-compose.yml, docker-compose.nas.yml
|
||||
├── Dockerfile.admin, apps/api-server/Dockerfile, apps/admin/Dockerfile
|
||||
├── apps/api-server/Dockerfile, apps/admin/Dockerfile, apps/web/Dockerfile
|
||||
├── turbo.json, tsconfig.json, tsconfig.base.json, pnpm-workspace.yaml
|
||||
├── package.json monorepo root, npm workspaces
|
||||
├── CLAUDE.md Claude-specific project rules
|
||||
|
|
@ -126,25 +126,22 @@ See [`03-shared-packages.md`](./03-shared-packages.md). Summary:
|
|||
|
||||
## 5. CI/CD
|
||||
|
||||
### GitHub Actions (`.github/workflows/`)
|
||||
|
||||
| Workflow | Purpose |
|
||||
|---|---|
|
||||
| `ci.yml` | Main CI: `code-quality` (secret scan, mobile release/config/build-config self-tests, Play asset contract, lint, typecheck), `api-server-tests`, `edge-functions-quality` (Deno), `test-matrix` (win/mac/ubuntu vitest), `build-validation` (desktop win, admin ubuntu), `mobile-android` (debug/CSPRNG/E2E APKs + verifiers), `mobile-emulator-e2e` (API 35 + Maestro 2.7.0) |
|
||||
| `release.yml` | On tag `v*.*.*`: preflight → `package-windows` (NSIS) → `package-macos` (DMG/ZIP arm64) → `package-android` (signed APK/AAB + evidence) → `package-admin-docker` (GHCR) → `publish-release` (checksums + GitHub Release + Forgejo canonical publish) |
|
||||
| `deploy-site.yml` | On `site/**`: build Vite site, boundary self-test, deploy GitHub Pages |
|
||||
| `build-mac.yml` | Manual macOS build (arm64/x64), sox + PyInstaller sidecar + electron-rebuild |
|
||||
| `payple-renew.yml` | Daily cron → `payple-renew` edge function |
|
||||
| `release-signing-ca.yml` | Manual Windows (Azure Trusted Signing) / macOS notarize build+sign |
|
||||
> `.github/workflows/` was deleted 2026-09-26 (Wave 3, dc43884): the repository has Forgejo and GitLab
|
||||
> remotes but no GitHub remote, so nothing under it ever ran — including the daily Payple renewal cron.
|
||||
> CI now runs only on Forgejo Actions (`.forgejo/workflows/`); see below.
|
||||
|
||||
### GitLab CI (`.gitlab-ci.yml`)
|
||||
|
||||
Stages `validate → test → build → e2e → package → publish → deploy`. Primary pipeline for desktop Windows/macOS releases (Forgejo Generic Registry is the canonical updater feed; GitLab project 1172 is a legacy mirror) and production mobile releases (`mobile-production-release`, manual/protected). Admin NAS deploy job is intentionally **disabled**. `package-windows`/`package-macos` build the faster-whisper sidecar (`sidecar:setup` → `sidecar:build`) and run `scripts/ci/verify-sidecar-bundle.mjs` before electron-builder, so a release can never ship without the local STT engine. Every pipeline that runs `npm run build --workspace=@d3ro/desktop` (`.forgejo` release/portable, `.github` CI/release) then runs `scripts/ci/verify-desktop-renderer-bundles.mjs`, which fails packaging when a renderer page references an asset the build did not emit (GAP-INFRA-05).
|
||||
Stages `validate → test → build → e2e → package → publish → deploy`. Primary pipeline for desktop Windows/macOS releases (Forgejo Generic Registry is the canonical updater feed; GitLab project 1172 is a legacy mirror) and production mobile releases (`mobile-production-release`, manual/protected). Admin NAS deploy job is intentionally **disabled**. `package-windows`/`package-macos` build the faster-whisper sidecar (`sidecar:setup` → `sidecar:build`) and run `scripts/ci/verify-sidecar-bundle.mjs` before electron-builder, so a release can never ship without the local STT engine. Every pipeline that runs `npm run build --workspace=@d3ro/desktop` (`.forgejo` CI/release/portable) then runs `scripts/ci/verify-desktop-renderer-bundles.mjs`, which fails packaging when a renderer page references an asset the build did not emit (GAP-INFRA-05).
|
||||
|
||||
### Forgejo Actions (`.forgejo/workflows/`)
|
||||
`ci.yml` — CI is now Forgejo-only (`.github/workflows` deleted 2026-09-26, Wave 3, dc43884). Jobs: `quality` (`version:check`, `contract:check`, `release:metadata:test`, secret scan, mobile release/config/build-config self-tests, Play asset contract, lint, typecheck, Vitest), `build-validation`, `mobile-quality`, `edge-functions` (Deno), `api-server-tests`.
|
||||
|
||||
`payple-renew.yml` — daily cron (+ manual dispatch) → `payple-renew` edge function; ported from the never-run `.github/workflows/payple-renew.yml` because the repository has no GitHub remote. Requires Forgejo secrets `SUPABASE_URL`, `CRON_SECRET`.
|
||||
|
||||
`portable.yml` — 태그/수동 실행으로 **서명 없이** portable 채널(95MiB 7z 분할 볼륨 + Scoop 매니페스트 + 설치 스크립트)을 게시한다. `WIN_CSC_*` 불필요, updater feed는 건드리지 않는다.
|
||||
|
||||
`deploy-site.yml` / `deploy-site-windows.yml` — build `site`, write release identity, deploy to Cloudflare Pages `d3ro` (`d3ro.pages.dev`), verify live commit/version, app-links, legal URLs. 커스텀 도메인 `d3ro.chanpaca.net` 은 Pages 커스텀 도메인이 DNS CNAME을 요구하므로, DNS를 건드릴 수 없는 동안은 Workers 라우트 브리지 `server/cloudflare-site-bridge/`(`d3ro.chanpaca.net/*` → Pages 프록시, 수동 `npx wrangler deploy`)가 서빙한다. CNAME을 추가한 뒤 브리지를 삭제하면 Pages 커스텀 도메인으로 직접 서빙된다(GAP-REL-09b).
|
||||
`deploy-site.yml` — 유일한 사이트 배포 경로(Linux, Cloudflare Pages). Windows 수동 중복 워크플로(`deploy-site-windows.yml`)는 2026-09-26 삭제됐다(dc43884). build `site`, write release identity, deploy to Cloudflare Pages `d3ro` (`d3ro.pages.dev`), verify live commit/version, app-links, legal URLs. 커스텀 도메인 `d3ro.chanpaca.net` 은 Pages 커스텀 도메인이 DNS CNAME을 요구하므로, DNS를 건드릴 수 없는 동안은 Workers 라우트 브리지 `server/cloudflare-site-bridge/`(`d3ro.chanpaca.net/*` → Pages 프록시, 수동 `npx wrangler deploy`)가 서빙한다. CNAME을 추가한 뒤 브리지를 삭제하면 Pages 커스텀 도메인으로 직접 서빙된다(GAP-REL-09b).
|
||||
`release.yml` — tag-triggered Windows build (signed) + `publish-forgejo-release.mjs` to the canonical Forgejo feed/release hub.
|
||||
|
||||
---
|
||||
|
|
@ -153,15 +150,15 @@ Stages `validate → test → build → e2e → package → publish → deploy`.
|
|||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `Dockerfile.admin` | 3-stage Next.js admin build (node 24.19.0-alpine, port 3001) |
|
||||
| `apps/admin/Dockerfile` | Next.js standalone runner for `.next/standalone` |
|
||||
| `apps/api-server/Dockerfile` | Multi-stage .NET 10 (sdk → aspnet runtime), port 5000, `VOLUME /app/data` |
|
||||
| `docker-compose.yml` | Dev/self-host: `d3ro-api-server` (5050→5000, `./data` volume), `d3ro-admin` (3001), optional `ollama` (profile `ai`, 11434) |
|
||||
| `docker-compose.nas.yml` | NAS: prebuilt `d3ro-voice-api:latest` + `d3ro-voice-admin:latest`; API mounts `/volume1/docker/d3ro/wwwroot/{privacy,terms,delete-account,legal.css}` read-only |
|
||||
| `apps/admin/Dockerfile` | Next.js standalone runner for `.next/standalone` (the old root `Dockerfile.admin` 3-stage build was a duplicate, deleted 2026-09-26, Wave 3, cd9d199) |
|
||||
| `apps/api-server/Dockerfile` | Multi-stage .NET 10 (sdk → aspnet runtime), port 5000, `VOLUME /app/data`; no longer serves static files (`wwwroot` removed — see `07-api-server.md`) |
|
||||
| `apps/web/Dockerfile` | Next.js standalone runner for `@d3ro/web`, basePath `/app`, port 3002 (added 2026-09-26, Wave 3, b6fe588) |
|
||||
| `docker-compose.yml` | Dev/self-host: `d3ro-api-server` (5050→5000, `./data` volume), `d3ro-admin` (3001), `d3ro-web` (3002, `@d3ro/web` basePath `/app`, Supabase/Payple keys as build args), optional `ollama` (profile `ai`, 11434) |
|
||||
| `docker-compose.nas.yml` | NAS: prebuilt `d3ro-voice-api:latest` / `d3ro-voice-admin:latest` / `d3ro-voice-web:latest` (3002) / `d3ro-whisper` (faster-whisper, OpenAI-compatible). No legal-doc `wwwroot` mount and no `NEXT_PUBLIC_API_URL` — both were dropped 2026-09-26 (Wave 3, b6fe588); legal pages are served only from `site/public`. |
|
||||
|
||||
Deploy scripts: `scripts/deploy-nas.ps1`, `scripts/deploy-nas.sh`, `scripts/deploy-site-to-nas.js`, `scripts/nas-control.sh` (start/stop/restart/status/logs/backup/update).
|
||||
Deploy scripts: `scripts/deploy-nas.ps1`, `scripts/deploy-nas.sh`, `scripts/nas-control.sh` (start/stop/restart/status/logs/backup/update). `scripts/deploy-site-to-nas.js` was deleted 2026-09-26 (Wave 3, cd9d199) — site deploy is Cloudflare Pages only (`.forgejo/workflows/deploy-site.yml`).
|
||||
|
||||
Public endpoints (production): `https://d3ro.chanpaca.net` — **랜딩/다운로드 센터**(2026-09-19부터 Pages `d3ro` 배포본을 Workers 라우트 브리지가 서빙; 그 이전에는 바인딩이 없어 빈 404였다), `https://admin.chanpaca.net` (admin CRM). Edge: `server/cloudflare-worker` proxying to the NAS origin, plus a **Cron Trigger** (`* * * * *`) that drains the Supabase push outbox via `send-push?mode=drain` (`src/push-drain.ts`; needs `SUPABASE_URL` var + `SUPABASE_SERVICE_ROLE_KEY` secret). Tunnel: Cloudflare Tunnel `kd-nas` (NAS 포털/API는 현재 이 호스트네임에 바인딩되어 있지 않다).
|
||||
Public endpoints (production): `https://d3ro.chanpaca.net` — **랜딩/다운로드 센터**(site/, Pages `d3ro` 배포본을 Workers 라우트 브리지가 서빙), `https://d3ro.chanpaca.net/app` — **웹앱**(`apps/web`, basePath `/app`; 사이트 브리지 워커가 `WEB_APP_ORIGIN`(터널 호스트)으로 전달하며, 호스트가 비어 있으면 `/app`은 503을 반환한다 — 터널 호스트 설정 대기), `https://admin.chanpaca.net` (admin CRM). Edge: `server/cloudflare-worker` proxying to the NAS origin, plus a **Cron Trigger** (`* * * * *`) that drains the Supabase push outbox via `send-push?mode=drain` (`src/push-drain.ts`; needs `SUPABASE_URL` var + `SUPABASE_SERVICE_ROLE_KEY` secret). Tunnel: Cloudflare Tunnel `kd-nas` (NAS 포털/API는 현재 이 호스트네임에 바인딩되어 있지 않다).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -178,8 +175,8 @@ Full detail: [`09-supabase-backend.md`](./09-supabase-backend.md).
|
|||
|
||||
## 8. `scripts/` groups
|
||||
|
||||
- **CI (`scripts/ci/`, 33 files):** build/version/release (`build-all`, `sync-version`, `generate-checksums`, `verify-release-metadata`, `create-release-tag`, `extract-release-notes`), security (`check-no-hardcoded-secrets`), mobile release gates (`verify-mobile-release-boundary/-config/-build-config`, `verify-android-artifact/-app-links`, `verify-play-store-assets`, `prepare-whisper-model`, `create-mobile-release-evidence`, `prepare-mobile-release-publication`, emulator/CSPRNG gates), keys (`create-desktop-license-keypair`, `create-release-evidence-key`, etc.), publish (`publish-forgejo-release` canonical, `publish-gitlab-release` mirror; legacy `sync-and-publish-forgejo-release`, `upload-asset-to-forgejo-release`), env/tooling (`bootstrap-linux-toolchain.sh`, `audit-nas-stt-config.ps1`, mobile local E2E scripts).
|
||||
- **Deploy/release:** `deploy-nas.ps1/.sh`, `deploy-site-to-nas.js`, `nas-control.sh`, `publish-gh.ps1`, `gen-keystore.js`.
|
||||
- **CI (`scripts/ci/`, 33 files):** build/version/release (`build-all`, `sync-version`, `generate-checksums`, `verify-release-metadata`, `create-release-tag`, `extract-release-notes`), security (`check-no-hardcoded-secrets`), mobile release gates (`verify-mobile-release-boundary/-config/-build-config`, `verify-android-artifact/-app-links`, `verify-play-store-assets`, `prepare-whisper-model`, `create-mobile-release-evidence`, `prepare-mobile-release-publication`, emulator/CSPRNG gates), keys (`create-desktop-license-keypair`, `create-release-evidence-key`, etc.), publish (`publish-forgejo-release` canonical, `publish-gitlab-release` mirror; the legacy `sync-and-publish-forgejo-release`/`upload-asset-to-forgejo-release` scripts were deleted 2026-09-26, Wave 3, cd9d199), env/tooling (`bootstrap-linux-toolchain.sh`, `audit-nas-stt-config.ps1`, mobile local E2E scripts).
|
||||
- **Deploy/release:** `deploy-nas.ps1/.sh`, `nas-control.sh`, `publish-gh.ps1`, `gen-keystore.js` (`deploy-site-to-nas.js` deleted 2026-09-26, Wave 3, cd9d199 — site deploy is Cloudflare Pages only).
|
||||
- **GCP/Google OAuth automation + inspection (~70 `*.mjs`):** `auto-configure-oauth`, `automate-google-oauth`, `setup-consent`, `create-*-client`, `check-*`, `inspect-*` — mostly one-off/browser-driven console automation.
|
||||
- **AdMob console automation:** `admob-probe.mjs` (read-only login/app/ad-unit probe), `admob-login.mjs` + `run-admob-login.bat` (one interactive headful Chrome login into a persistent profile), `admob-automate.mjs` (dry-run by default; `--apply` creates/verifies banner+rewarded units and reports Play-store link). Uses `playwright` with `channel: 'chrome'` and the gitignored `.chrome-playwright-profile`.
|
||||
- **E2E / verification:** `e2e-desktop-*.js`, `real-app-multi-tab-e2e.js`, `test-and-capture-all-10-ad-services.js`, `verify-live-production-d3ro.js`.
|
||||
|
|
@ -201,7 +198,7 @@ Full detail: [`09-supabase-backend.md`](./09-supabase-backend.md).
|
|||
| `apps/desktop/electron-builder.yml` | appId `com.d3ro.voice`, NSIS x64 (forced code signing), macOS DMG/ZIP arm64, generic Forgejo publish feed, asarUnpack native modules + `@ffmpeg-installer`, extraResources (icons, sounds, sox, **sidecar**, ffmpeg, ollama) |
|
||||
| `apps/desktop/src/main/update-feed.ts` | Auto-update feed SSOT (canonical Forgejo + legacy GitLab mirror, channels) |
|
||||
| `release/update-policy.json` | Update policy SSOT (channels, minimum supported version, forced update, delta/full, staged rollout, kill switch) |
|
||||
| `apps/web/src/lib/desktop-release.ts`, `site/src/release.ts` | Download-center desktop release contract (installer filename + release date); version and date are kept on the SSOT by `npm run version:sync` (drifted to 1.2.0 once — GAP-REL-08) |
|
||||
| `site/src/release.ts` | Download-center desktop release contract (installer filename + release date); version and date are kept on the SSOT by `npm run version:sync` (drifted to 1.2.0 once — GAP-REL-08). `apps/web/src/lib/desktop-release.ts` and the `apps/web` download/releases pages were deleted 2026-09-26 (Wave 3, cd9d199) — `site/` is the only download surface, and `/download`/`/releases` on `apps/web` now redirect to it |
|
||||
| `apps/desktop/src/main/update-policy.ts` | Policy parsing/decision logic |
|
||||
| `scripts/ci/publish-forgejo-release.mjs` | Canonical Forgejo registry + Release + feed publisher |
|
||||
|
||||
|
|
@ -227,5 +224,5 @@ Version sync is enforced by `scripts/ci/sync-version.mjs` and `verify-release-me
|
|||
|
||||
See [`11-gap-backlog.md`](./11-gap-backlog.md) for the maintained list (`INFRA-*`). Headlines:
|
||||
- `apps/mobile-rn` is not an npm workspace member; use `typecheck:mobile`/`lint:mobile`/`test:mobile` or `verify:all`.
|
||||
- Admin NAS deploy job disabled in GitLab CI; admin ships via GitHub/GHCR + manual NAS compose.
|
||||
- Admin NAS deploy job disabled in GitLab CI; the `package-admin-docker`→GHCR publish step lived only in the now-deleted `.github/workflows/release.yml` (2026-09-26, dc43884) and was not ported to Forgejo, so admin image publish + NAS compose update is manual (`GAP-INFRA-02`).
|
||||
- Two identity systems (.NET JWT/SQLite vs Supabase); a canonical resolver now exists in `@d3ro/core/entitlement` but web/mobile/.NET adoption is incremental (`11` GAP-ID-02).
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@
|
|||
- **Policies:** `ManagerOrAbove` (manager/admin/superadmin), `AdminOrAbove` (admin/superadmin), `SuperAdminOnly` (superadmin). Role normalization strips `_`/`-`, lowercases.
|
||||
- **CORS/Hosts:** strict origin validation (`CORS_ALLOWED_ORIGINS`), `ALLOWED_HOSTS` required outside Development.
|
||||
- **Startup DB init:** `EnsureCreated()`, raw `CREATE TABLE IF NOT EXISTS` for admin operation/audit tables, legacy SHA-256 password lockdown (`IsActive=false`, `Role="LegacyDisabled"`), idempotent env admin provisioning (`ADMIN_EMAIL`/`ADMIN_PASSWORD`, only when no active user), default LLM/STT endpoints seeded.
|
||||
- **Middleware order:** Swagger (dev) → CORS → invite-page hardening (CSP/no-store) → default files → mobile/legacy asset block (404 for `.apk`/`.aab`/signed zips + legacy bundles) → static files → rate limiter → authentication → authorization.
|
||||
- **Middleware order:** Swagger (dev) → CORS → rate limiter → authentication → authorization. The API no longer serves static files: `wwwroot` (stale site build, download/invite pages, `.well-known` copy, legacy embedded admin SPA, 1.0.0 binaries) was deleted 2026-09-26 (Wave 3, cd9d199), and `UseStaticFiles`/the mobile-asset-block fallbacks were removed from `Program.cs` along with it. The Next.js admin (`apps/admin`) is the only admin UI.
|
||||
- **Health:** `GET /health`, `GET /api/health` → `{status, service, version, uptimeSeconds, database, timestamp}`.
|
||||
- **Fallbacks:** `/accept-invite` → `accept-invite.html`; `/admin/{*path}` → legacy embedded admin SPA in `wwwroot/admin`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -102,6 +101,6 @@ DTOs (`Dtos/Dtos.cs`): auth, license audit, LLM, admin/model endpoints, STT (tra
|
|||
- Known intentional states (not bugs):
|
||||
- Legacy SHA-256 users force-disabled.
|
||||
- No hardcoded/seeded admin credentials.
|
||||
- Mobile/legacy release assets blocked with 404.
|
||||
- No static file serving — `wwwroot` and its mobile/legacy release asset block were deleted 2026-09-26 (Wave 3, cd9d199); downloads/releases are served only from `site/`.
|
||||
- `LlmProxyService` Mock fallback.
|
||||
- This backend holds a **separate identity** from Supabase; see `11-gap-backlog.md` `ID-01`.
|
||||
|
|
|
|||
|
|
@ -88,4 +88,4 @@ Root files: `instrumentation.ts` (startup security validation), `proxy.ts` (edge
|
|||
- `admin-payments` Payple live history → `501`.
|
||||
- `UnavailableAdminPanel` whenever Supabase env absent; writes disabled, no sample metrics.
|
||||
- `/pipelines` and dashboard node/error sections render only measured data.
|
||||
- Deploy: `Dockerfile.admin` / `apps/admin/Dockerfile` → GHCR + NAS compose; GitLab admin NAS deploy job disabled.
|
||||
- Deploy: `apps/admin/Dockerfile` → GHCR + NAS compose; the duplicate root `Dockerfile.admin` was deleted 2026-09-26 (Wave 3, cd9d199) since `apps/admin/Dockerfile` is the one actually used. GitLab admin NAS deploy job disabled.
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Migration numbering referenced in SSOT goes up to `00028`; CI verifies `migratio
|
|||
| `send-push` | Push delivery |
|
||||
| `account-delete` | Account deletion cascade + provider unlink |
|
||||
| `admin-users` / `admin-subscriptions` / `admin-payments` / `admin-audit-log` | Admin operations |
|
||||
| `billing-catalog` | Server pricing catalog |
|
||||
| `billing-catalog` | Server pricing catalog. Prices/quotas read `PLAN_PRICE_KRW`/`PLAN_QUOTA` from `functions/_shared/core-contract.generated.ts`, generated from `packages/core/src/plan-catalog.ts` by `scripts/ci/sync-core-contract.mjs` (`npm run contract:check` fails on drift; added 2026-09-26, Wave 3, 88f24d8) |
|
||||
| `stripe-checkout` / `stripe-portal` / `stripe-webhook` | Stripe billing |
|
||||
| `payple-checkout` / `payple-manage` / `payple-renew` / `payple-webhook` | Payple billing (Korea) |
|
||||
| `iap-verify` | Google Play / App Store purchase verification |
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ end-to-end behaviour has **not been verified by typing in a real app** (`11` GAP
|
|||
| SHELL-08 | Auto-update | [x] | [-] | [!] | [-] | Desktop electron-updater; Forgejo canonical feed; channels + mandatory/major-vs-delta policy (`release/update-policy.json`); mobile store updates |
|
||||
| SHELL-09 | Support / diagnostics | [x] | [ ] | [ ] | [~] | Desktop `SupportModal`; admin `/support` stub |
|
||||
| SHELL-10 | Download center / releases | [-] | [x] | [-] | [x] | Web `/download`, admin `/releases`, Forgejo |
|
||||
| SHELL-11 | Landing site / legal pages | [-] | [-] | [-] | [-] | `site/` — privacy/terms/delete-account live; 2026-09-26 truthfulness/a11y cleanup (facts from code SSOT, pricing `site/src/pricing.ts`, 10 locales fully translated, footer links legal pages) — see `design.md` |
|
||||
| SHELL-11 | Landing site / legal pages | [-] | [-] | [-] | [-] | `site/` — the only landing/download/legal/invite/assetlinks surface (Wave 3, 2026-09-26: copies in api-server wwwroot and apps/web removed; 404.html; hero loops a copy of the app recording capsule). Prices/URLs from `packages/core` plan-catalog/web-urls — see `design.md` |
|
||||
| SHELL-12 | Notifications (in-app / desktop) | [x] | [ ] | [x] | [x] | Desktop events; mobile push |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -26,7 +26,13 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res
|
|||
| GAP-REL-08 | Release | 다운로드 센터가 **존재하지 않는 설치 파일**을 가리켰다. `apps/web/src/lib/desktop-release.ts`와 `site/src/release.ts`의 `DESKTOP_VERSION`이 `1.2.0`에 멈춰 있어 설치 URL이 `D3RO-Voice-Setup-1.2.0-x64.exe`였고, 그 경로는 피드에서 404다(실측: 1.2.0=404, 1.3.7=206). `version:sync`가 이 두 표면을 덮지 않아 계속 어긋났다. | `scripts/ci/sync-version.mjs`, `apps/web/src/lib/desktop-release.ts`, `site/src/release.ts` | `[x]` 2026-09-19: 두 다운로드 계약 파일을 `sync-version.mjs` 대상에 추가해 버전·릴리스일이 SSOT에서 자동 반영되도록 하고, 현재 값(1.3.7 / 2026-09-19)으로 정정했다. `version:check`·typecheck·site 빌드 GREEN. |
|
||||
| GAP-REL-09 | Release | 랜딩 사이트가 **재배포되지 않는다**. `deploy` 워크플로가 main push마다 실패한다. 실측 원인(run#66 로그): `site/src/sections/Hero.tsx`가 타이머 ref를 `NodeJS.Timeout`으로 타이핑해 `@types/node` 네임스페이스가 필요했고, 배포 잡은 `npm ci --prefix site`만 하므로 조상 `node_modules`의 hoisted 타입이 없어 `tsc -b`가 `TS2503: Cannot find namespace 'NodeJS'`로 실패한다. 그래서 `https://d3ro.chanpaca.net/release-identity.json`이 404다(공개 버전 검증 불가). | `.forgejo/workflows/deploy-site.yml`, `site/src/sections/Hero.tsx` | `[x]` 2026-09-19: ref를 `ReturnType<typeof setTimeout>`으로 바꿔 hoisted 타입 의존을 제거했다(격리 `--typeRoots`로 CI 조건 재현 → 수정 전 TS2503, 수정 후 clean). 같은 수정을 push하자 `deploy` run#67이 사이트 빌드를 통과해 `dist/`를 만들었고, 실패는 다음 단계(Cloudflare)로 이동했다. |
|
||||
| GAP-REL-09b | Release | `d3ro.chanpaca.net`이 404였던 직접 원인: 이 Cloudflare 계정에 Pages 프로젝트 `d3ro`/`d3ro-voice`가 **존재하지 않아** 커스텀 도메인 바인딩이 없었다(빈 본문 404, `cf-ray`만 반환). Pages 커스텀 도메인은 존 DNS CNAME(`d3ro → d3ro.pages.dev`)을 요구하는데 기존 `d3ro` 레코드가 남아 있어 `CNAME record not set`으로 pending에 머물렀고, 로컬 wrangler 자격증명에는 DNS 스코프가 없다(403 Authentication error). `deploy-site.yml`은 `CF_API_TOKEN` 시크릿이 없어 마지막 게시 단계에서도 `exit 1`이다. | `server/cloudflare-site-bridge/`, `.forgejo/workflows/deploy-site.yml`, `docs/map/02-infrastructure.md` | `[x]` 2026-09-19: Pages 프로젝트 `d3ro` 생성 + `site/dist` production 배포(`d3ro.pages.dev` 200, `release-identity.json` = commit `2407f5a` / 1.3.7) + 커스텀 도메인 연결. DNS 없이 도메인을 살리기 위해 Workers 라우트 브리지(`server/cloudflare-site-bridge`, `d3ro.chanpaca.net/*` → Pages 프록시, `npx wrangler deploy`)를 배포 → 라이브 확인: `/`·`/privacy/`·`/terms/`·`/delete-account/` 200, 라이브 번들이 설치 파일명을 `1.3.7`로 계산, `/download.html` → `/#download`. 남은 정리 2건: (1) 대시보드에 CNAME을 추가한 뒤 브리지 워커 삭제, (2) CI 자동 게시를 위해 `CF_API_TOKEN`(Pages/Workers Edit) + `CF_ACCOUNT_ID`=`8e83cc130e7329c160cf2b88d6b4c20a`를 Forgejo 시크릿에 등록. **2026-09-23:** CI `deploy`(deploy-site.yml)는 `CF_API_TOKEN` 부재로 run#71까지 여전히 실패한다. 로컬 인증 wrangler(pages:write)로 `npm run build --prefix site` → release-identity 작성 → `npx wrangler pages deploy site/dist --project-name d3ro --branch main`을 수동 배포했고, 라이브 `https://d3ro.chanpaca.net/release-identity.json`이 commit `5c11ee2` / version `1.5.0`을, 라이브 번들이 설치 파일명 `D3RO-Voice-Setup-1.5.0-x64.exe`를 보고한다. 자동 게시에는 여전히 시크릿 등록이 필요하다. |
|
||||
| GAP-BILL-01 | Billing | 랜딩 사이트 가격(Pro ₩2,900 / Pro+ ₩8,900, 2026-09-26 사용자 확정)과 서버 결제 카탈로그(Payple Pro ₩9,900 / Pro+ ₩29,900)가 다르다. 결제 진입 URL도 `payment-handlers.ts`(`d3ro.chanpaca.net/billing`)와 `license-handlers.ts`(`d3ro.dev/billing`)로 갈린다. | `site/src/pricing.ts`, `server/supabase/functions/_shared/payple.ts`, `server/supabase/functions/_shared/billing-catalog.ts`, `apps/desktop/src/main/ipc/license-handlers.ts` | `[ ]` 2026-09-26: 사이트는 새 가격을 표시하고 `d3ro.chanpaca.net/billing?tier=`로 연결한다. 서버 카탈로그 금액 변경(Payple·Stripe 가격 ID)과 결제 URL 정본 확정이 남았다. |
|
||||
| GAP-BILL-01 | Billing | 사이트 가격과 서버 청구 금액·결제 진입 URL이 제각각이었다. | `packages/core/src/plan-catalog.ts`, `packages/core/src/web-urls.ts`, `scripts/ci/sync-core-contract.mjs` | `[x]` 2026-09-26 (Wave 3, 88f24d8·b6fe588): 가격·쿼터·공개 URL 정본을 core 두 파일로 합치고 Deno는 생성 사본(`contract:check`). Payple 함수 4종+billing-catalog 운영 재배포(payple-* v9, billing-catalog v5) → 신규·갱신 모두 ₩2,900/₩8,900. 결제 진입은 `/app/billing` 하나. |
|
||||
| GAP-BILL-02 | Billing | **Payple 정기 갱신이 한 번도 실행되지 않았다.** 갱신 cron(`payple-renew`)이 `.github/workflows`에만 있었는데 GitHub 원격이 없다. 또 웹 Payple 결제용 `NEXT_PUBLIC_PAYPLE_CLIENT_KEY`가 어디에도 설정된 적이 없어 웹 Payple 결제 버튼이 꺼져 있다. | `.forgejo/workflows/payple-renew.yml`, `apps/web/src/components/billing/payple-checkout-button.tsx`, `apps/web/Dockerfile` | `[!]` EXT 2026-09-26: 워크플로는 Forgejo로 이식(dc43884). 남은 일 — Forgejo 시크릿 `SUPABASE_URL`·`CRON_SECRET` 등록, Payple 클라이언트 키를 웹 이미지 build arg로 넣어 재빌드, Stripe(USD)·Google Play 가격을 각 콘솔에서 새 가격으로. |
|
||||
| GAP-WEB-01 | Web | 웹앱(`apps/web`)이 공개되지 않았다(배포 경로가 없었음). | `apps/web/Dockerfile`, `docker-compose.nas.yml`, `server/cloudflare-site-bridge` | `[~]` 2026-09-26: `/app` basePath·standalone 이미지·NAS 컨테이너 `d3ro_voice_web`(3002). 남은 일(EXT) — Cloudflare Tunnel 공개 호스트(예: `d3ro-app.chanpaca.net` → NAS 3002) 추가 후 브리지 워커 `WEB_APP_ORIGIN` 설정·배포, Supabase 대시보드 Redirect URLs에 `https://d3ro.chanpaca.net/app/**` 추가. 그 전까지 `/app/*`는 사이트 404. |
|
||||
| GAP-OPS-01 | Ops | NAS 운영 compose가 저장소와 어긋나 있다. 운영본은 JWT 비밀값 기본값 폴백·법률 wwwroot 마운트를 쓰고, `.env`에 `ADMIN_BOOTSTRAP_TOKEN`·`API_SERVER_URL`·`SUPABASE_URL`·`SUPABASE_SERVICE_ROLE_KEY`가 없어 저장소 compose(`:?` 필수)로 바꾸면 api/admin이 기동하지 않는다. | `docker-compose.nas.yml`, NAS `/volume1/docker/d3ro/.env` | `[ ]` 2026-09-26: 운영본에만 있던 `d3ro-whisper`는 저장소로 합쳤다. 누락 변수를 채운 뒤 저장소 compose로 교체해야 한다. |
|
||||
| GAP-CI-01 | CI | macOS 빌드·서명 러너가 없다. `.github`의 build-mac·release-signing-ca는 실행된 적 없이 삭제됐다. | `.forgejo/workflows/*`, `.gitlab-ci.yml` | `[!]` EXT: Mac 호스트에 Forgejo runner(`macos` 라벨)를 붙이거나 GitLab `package-macos` 사용. 서명된 Android 릴리스는 GitLab `mobile-production-release`가 정본. |
|
||||
| GAP-I18N-02 | Desktop | 녹음 캡슐의 "처리 중..."이 `recording-tip/index.html`에 한국어로 고정돼 앱 언어를 따르지 않는다. | `apps/desktop/src/renderer/popups/recording-tip/*`, `WindowManager.getPopupI18nStrings` | `[ ]` 2026-09-26 발견. |
|
||||
| GAP-TEAM-02 | Team | 브라우저에서 초대를 수락하는 경로가 없다. 웹 `accept-invite` 페이지는 발급 링크가 가리키지 않고 로그인 후 토큰을 읽는 곳이 없어 끊겨 있었으므로 삭제했다(사이트 `/accept-invite/`는 앱 딥링크만). | `site/public/accept-invite/`, `server/supabase/functions/team-accept` | `[ ]` 2026-09-26: 데스크톱 전용 사용자를 위한 웹 수락 흐름이 필요하면 `/app` 아래에 다시 설계. |
|
||||
| GAP-REL-10 | Release | `release-windows`(태그 파이프라인)는 서명 가드에 도달하기 **전에** sidecar 단계에서 죽는다. 이 러너 컨텍스트에서는 `sidecar:setup`이 Python 3.11+를 찾지 못한다(`Python 3.11+ 를 찾을 수 없습니다`) → `sidecar:build` → `verify-sidecar-bundle.mjs` 연쇄 실패(실측: run#65 `v1.3.7`, run#61 `v1.3.6`). 같은 러너의 portable 잡은 `py -3.11 → Python 3.11.9`를 찾아 사이드카 빌드에 성공하므로, 워크플로/컨테이너 간 PATH 차이다. | `.forgejo/workflows/release.yml`, `apps/desktop/scripts/setup-sidecar.mjs` | `[!]` 2026-09-19: 러너에 Python 3.11+(`py` 런처 포함)를 보장하거나 워크플로에 `actions/setup-python` 단계를 추가한다. 그 전까지 서명 게시는 불가능하다(GAP-REL-02와 별개 선행 차단). |
|
||||
| GAP-REL-11 | Release | portable 워크플로의 마지막 `actions/upload-artifact@v4` 단계가 Forgejo 러너에서 `GHESNotSupportedError`로 실패한다(증거 보존만 실패, 게시는 성공). | `.forgejo/workflows/portable.yml` | `[x]` 2026-09-19: `v1.3.7` portable 게시는 run#64에서 성공(7z 단일 볼륨 83.7MB + zip 2부, `portable-latest/portable.json`이 1.3.7 보고). 남은 조치: upload-artifact 단계를 제거하거나 v3/다른 보존 방식으로 바꿔 워크플로를 GREEN으로 만든다. |
|
||||
| GAP-REL-12 | Release | Forgejo generic registry는 파일 단위 덮어쓰기를 거부(HTTP 409)하는데, portable 게시 스크립트의 "동일 파일 건너뛰기"가 1MiB 초과 파일을 **크기만** 비교했다. 그래서 새 runtime의 sidecar 볼륨 `.001`이 이전 볼륨과 정확히 같은 94,371,840 bytes였고, 내용이 다른데도 "동일"로 오판해 `runtime-latest`가 낡은 `.001` + 새 `.002`로 갈라졌다(다운로드 시 손상). | `scripts/ci/publish-portable-release.mjs` | `[x]` 2026-09-23: 파일 목록 API(`GET /api/v1/packages/{owner}/generic/{name}/{version}/files`)의 sha256으로 정확히 비교하고, 내용이 다른 파일이 하나라도 있으면 해당 별칭 버전 전체를 삭제 후 재업로드하도록 수정했다. 이미 발생한 `runtime-latest`는 버전을 지우고 재게시해 4개 파일 모두 로컬 해시와 일치함을 확인했다. |
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Phase 7: 테스트 + 빌드 + 배포
|
||||
|
||||
> 2026-09-26: `.github/workflows`는 삭제되었고 CI는 `.forgejo/workflows`가 정본이다 — 아래 `7.9 CI/CD (GitHub Actions)` 절은 최초 계획 시점의 스냅샷이다.
|
||||
|
||||
## 목표
|
||||
테스트 커버리지를 확보하고, 프로덕션 빌드/패키징 파이프라인을 구축하여 Windows 설치 파일을 생성한다. CI/CD를 설정하여 자동 빌드 및 배포를 가능하게 한다.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
> 본인 Mac에서 D3RO Voice를 dev 실행하거나 dist(.dmg/.zip) 빌드하기 위한 단계별 가이드.
|
||||
> Windows에서 작성된 코드는 V2-5에서 모든 플랫폼 분기를 추가했습니다 — Mac에서는 별도 코드 수정 없이 아래 단계만 따르면 됩니다.
|
||||
> 2026-09-26: `.github/workflows`는 삭제되었고 CI는 `.forgejo/workflows`가 정본이다 — 아래 `9. CI 빌드 (GitHub Actions)` 절은 작성 시점의 스냅샷이다.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
> 데이터·권한 정본: `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`의 미래 로드맵을 대체하는 구현·검증 정본이다.
|
||||
> 2026-09-26: `.github/workflows`는 삭제되었고 CI는 `.forgejo/workflows`가 정본이다 — 아래 날짜별 기록에 남은 `.github/workflows` 언급은 해당 시점의 스냅샷이다.
|
||||
|
||||
## 0. 2026-09-16 출시 게이트 스냅샷
|
||||
|
||||
|
|
@ -367,7 +368,7 @@
|
|||
| 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.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 |
|
||||
| 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. **2026-09-26 Wave 3 갱신(cd9d199):** `apps/web/public`과 `apps/api-server/wwwroot` 사본은 삭제됐고, `site/public`이 유일한 source가 됐다(`scripts/ci/verify-android-app-links.mjs`도 `site/public`만 검사) — live 재검증 필요성은 그대로다 | `site/public/.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 |
|
||||
| Release evidence signing identity | Ed25519 keypair creation + public-key fingerprint + sign/verify roundtrip | private key는 `C:\Users\encep\.d3ro\release\d3ro-mobile-evidence-ed25519-private.pem` user-only ACL, public key는 `release/mobile-release-evidence-public.pem`이다. keyId `2797d3e63affd2348941bc2871b57e520c958f7f5da1a4bbe8aa46904a890b7f`, pair/roundtrip GREEN. CI private-key secret·오프라인 복구 백업·실제 signed evidence는 대기 | `release/mobile-release-evidence-public.pem`, `release/android-release-identity.json`, `scripts/ci/verify-release-evidence-key-pair.mjs` | 2026-08-29 |
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
> 2026-08-21 Git 기준선: `main` @ `a9c9a1ca6efdfc83ae7a12a0c1f2a05f15ef9138` (역사 증거, 현재 release SHA 아님)
|
||||
> release identity: version `1.1.0`, Android versionCode / iOS build `1010001`
|
||||
> 판정: **내부 기능·local upload/evidence key·AdMob identity는 준비됐지만 production AAB·Firebase·CI secret 주입·closed test가 없어 Play 출시는 RED**
|
||||
> 2026-09-26: `.github/workflows`는 삭제되었고 CI는 `.forgejo/workflows`가 정본이다 — 아래 `.github/workflows` 언급은 작성 시점의 스냅샷이다.
|
||||
|
||||
## 0. 2026-08-29 release readiness 스냅샷
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
기준일: 2026-09-16. 체크 표시는 이 문서를 읽은 사람이 실제 증거를 확인한 뒤에만 바꾼다. 소스 검사, debug/E2E APK, HTTP 200 하나만으로 release GREEN을 선언하지 않는다.
|
||||
|
||||
> 2026-09-26: `.github/workflows`는 삭제되었고 CI는 `.forgejo/workflows`가 정본이다(dc43884) — 아래 `.github/workflows` 언급은 기준일(2026-09-16) 시점의 스냅샷이다. 서명 Android production release job은 Forgejo로 이식되지 않고 `.gitlab-ci.yml`의 `mobile-production-release`(수동/protected)에 그대로 남았다.
|
||||
|
||||
### 2026-08-29 Play Console live 스냅샷
|
||||
|
||||
| 항목 | 확인된 현재 사실 | 판정 |
|
||||
|
|
@ -99,9 +101,9 @@
|
|||
- [ ] CI의 AAB signer 검증에는 upload SHA-256을 사용했다.
|
||||
- [ ] Firebase OAuth, Google sign-in과 HTTPS App Links에는 **Play app-signing** SHA-1/SHA-256을 등록했다.
|
||||
|
||||
**현재 blocker:** `site/public`, `apps/web/public`, `apps/api-server/wwwroot`의 source `assetlinks.json` 3개는 확인된 Play app-signing SHA-256으로 교체했다. 그러나 live `https://d3ro.chanpaca.net/.well-known/assetlinks.json`은 아직 폐기된 과거 certificate를 반환하므로 배포와 public 재검증이 필수다. upload certificate는 `assetlinks.json`에 넣지 않는다.
|
||||
**현재 blocker:** `site/public`의 source `assetlinks.json`은 확인된 Play app-signing SHA-256으로 교체했다. (기준일 당시엔 `apps/web/public`, `apps/api-server/wwwroot`에도 사본이 있어 source가 3곳이었으나, 2026-09-26 Wave 3(cd9d199)에서 그 두 사본이 삭제되어 `site/public`이 유일한 source다.) 그러나 live `https://d3ro.chanpaca.net/.well-known/assetlinks.json`은 아직 폐기된 과거 certificate를 반환하므로 배포와 public 재검증이 필수다. upload certificate는 `assetlinks.json`에 넣지 않는다.
|
||||
|
||||
코드 경계: `.github/workflows/release.yml`은 `ANDROID_UPLOAD_CERT_SHA256`과 `ANDROID_PLAY_APP_SIGNING_CERT_SHA256`을 분리하고, `scripts/ci/mobile-release-evidence-lib.mjs`와 App Links verifier가 폐기 certificate를 거부해야 한다.
|
||||
코드 경계: Android production release job(`.gitlab-ci.yml`의 `mobile-production-release`)은 `ANDROID_UPLOAD_CERT_SHA256`과 `PLAY_APP_SIGNING_CERT_SHA256`을 분리하고, `scripts/ci/mobile-release-evidence-lib.mjs`와 App Links verifier(`scripts/ci/verify-android-app-links.mjs`, `site/public`만 검사)가 폐기 certificate를 거부해야 한다.
|
||||
|
||||
### Firebase
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue