A five-second phone recording took over a minute: stt-proxy waited up to
60 s for the self-hosted gateway, whose GPU endpoint was off and whose NAS CPU
Whisper needs 30-90 s per clip. With a direct provider configured the gateway
now gets 5 s plus the clip length (30 s cap).
The direct OpenAI fallback never produced a result. The production key held
characters that are not valid in an HTTP header, so every request threw while
being built; provider keys are now stripped of BOM/zero-width characters and a
still-invalid key counts as not configured. whisper-1 verbose_json reports the
language by name, which the result contract rejected; names now map to codes.
Fail-closed responses list each provider's failure (status or error class,
no secrets) so an outage can be diagnosed without log access.
Stripe is not used. Keeping its checkout, portal and webhook paths meant a
second payment provider, a second return-URL format and dead UI.
- Delete the stripe-checkout, stripe-portal and stripe-webhook functions and
their config; billing-catalog serves Payple prices only, and the web parser
rejects a catalog that still mixes in Stripe prices.
- Web: drop the Stripe checkout/portal buttons, provider toggle and return
notices; billing shows Payple only. Past rows with provider='stripe' are
still displayed ("Stripe (종료)") with a support contact instead of a portal.
- Desktop: delete the Stripe checkout modal, payment IPC channels, preload
namespace and their types; "Remove ads with Pro" opens the web billing page
via license.openBilling. Support/refund copy names Payple.
- billingUrl() loses the Stripe-only success/canceled result option; the
Deno contract is regenerated.
- Migrations and the DB's accepted provider values are untouched (history).
- Docs and the backlog record the removal (MON-04, EXT-STRIPE-01, GAP-BILL-03).
Verified: typecheck (desktop/web/admin/api-client/mobile), contract:check,
deno check all functions, deno test 80/80, desktop 1478/1480 on the Electron
runtime (2 known environment failures), web and admin builds, release
metadata and mobile boundary self-tests, eslint on changed files.
Since 2026-09-19 the site bridge worker served every path from Pages, so the
API server on the NAS had no public route: admin login (API_SERVER_URL)
and stt-proxy (D3RO_API_URL) called https://d3ro.chanpaca.net/api/... and
got the landing page's 405.
- The worker passes /app, /api and /health through to the domain origin
(the kd-nas tunnel) and serves everything else from Pages.
- The kd-nas tunnel sends path ^/app on d3ro.chanpaca.net to the web app
(NAS 3002); other paths keep going to the API (NAS 5050). No extra
hostname or DNS record is needed, so WEB_APP_ORIGIN is removed.
- API_PATH_PREFIXES joins WEB_APP_BASE_PATH in packages/core/src/web-urls.ts
(contract regenerated).
Verified live: /app/login 200, /health and /api/health 200, API login 401 for
an unknown account (was 405), landing/legal/404 unchanged, git/sso/admin
hosts unaffected.
Prices, quotas and site URLs were copied by hand into the edge functions,
admin, desktop and the landing site, and the copies disagreed (Payple billed
9,900/29,900 KRW, admin labels said 12,900/24,900 KRW and $9.9/$19.9, the
site said 2,900/8,900 KRW).
- packages/core/src/plan-catalog.ts is the single source for PLAN_PRICE_KRW
(Free 0 / Pro 2,900 / Pro+ 8,900 a month) and PLAN_QUOTA.
- packages/core/src/web-urls.ts is the single source for the public origin,
the /app web-app base path, SITE_URLS and billingUrl().
- Deno cannot bundle packages/core, so scripts/ci/sync-core-contract.mjs
generates _shared/core-contract.generated.ts; `npm run contract:check`
fails on drift (same pattern as version:sync).
- Payple checkout, renewal and webhook amount checks now bill the catalog
price, so existing subscribers move to the new price at their next renewal.
quota.ts, team-contract.ts and the tests read the generated values.
- Admin MRR/ARR is computed in KRW from the catalog; license labels, the
release link and desktop PREMIUM_LLM limits derive from core; the site
imports prices and quotas directly.
Policy: docs/REFACTOR_POLICY.md Wave 3, W3-1 and W3-2.
Every notification depended on Firebase Cloud Messaging, so a missing
Firebase project, which is the current state, meant no notification could be
delivered on any platform. Web Push and token-based Apple Push are now first
class transports alongside FCM, chosen per registered device, and a scheduled
Cloudflare Worker drain retries an outbox so a provider outage delays rather
than drops a message.
server/supabase/ 신규 디렉토리:
config.toml — Supabase CLI 설정
- 프로젝트 ID, DB 포트, auth providers (Google/GitHub/Apple),
edge_runtime, functions.*.verify_jwt 설정
migrations/ (PostgreSQL DDL, 시간순):
- 20260409000001_initial_schema.sql
12개 테이블: profiles, teams, team_members, meetings,
meeting_memos, meeting_documents, transcripts, history,
dictionary, memo_tags, daily_usage, subscriptions
+ 인덱스 + FK CASCADE + transcripts Realtime publication
- 20260409000002_rls_policies.sql
개인 전용(history/dictionary): user_id = auth.uid()
팀 공유(meetings 등): 본인 OR team_members 조회 subquery
daily_usage: 읽기만, 쓰기는 service_role RPC
- 20260409000003_auth_triggers.sql
handle_new_user — auth.users INSERT → profiles+subscriptions 자동 생성
moddatetime — 8개 테이블 updated_at 자동 갱신
increment_daily_usage — service_role 전용 쿼터 RPC
- 20260409000004_storage_buckets.sql
audio/exports/avatars 3개 버킷 + 경로 기반 접근 정책
(파일 경로가 {user_id}/...로 시작해야 쓰기 허용)
functions/ (Deno/TypeScript Edge Functions):
- _shared/cors.ts — CORS 헤더 + preflight 핸들러
- _shared/auth.ts — requireUser (JWT 검증 + User 반환)
- _shared/quota.ts — 티어별 쿼터 체크 + consume + service role client
- stt-proxy/index.ts — Google Cloud STT 래퍼 스캐폴딩
placeholder 응답, 실제 API 호출 코드는 주석으로 포함
- llm-proxy/index.ts — Anthropic Messages API 래퍼 스캐폴딩
티어별 허용 모델 정책 (free=Haiku, pro=Sonnet, team=Opus)
설계 문서:
- docs/v2/phase-V2-2.md — 상세 설계 (스키마/RLS/Edge Functions/Realtime)
- docs/v2/phase-V2-2-setup.md — 사용자 액션 가이드 (Supabase 계정/
OAuth 등록/CLI/배포/검증)
apps/desktop typecheck 통과 (server/는 Deno 런타임이라 별도).
실제 Supabase 프로젝트 배포는 사용자가 phase-V2-2-setup.md 따라 수행.