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:
Yun Chan 2026-09-26 16:02:44 +09:00
parent 92978607da
commit e87ce63440
17 changed files with 187 additions and 46 deletions

View file

@ -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).