fix(desktop+supabase): 로컬 ID UUID 통일 + Realtime publication (빅뱅 Phase 5 Part 2)
SaaS [9] — 실증 A/B/C/D 전부 통과, 로컬→클라우드 push 최초 성공(pushed=1). ## Bug 4: 로컬 nanoid PK vs Supabase UUID PK 불일치 - 증상: Push history failed: invalid input syntax for type uuid: "fvy6bIzr..." - 원인: 로컬 drizzle schema는 text PK + nanoid() 생성, Supabase는 uuid PK. 빅뱅 사이클 내내 push가 한 번도 성공한 적 없었음 (지난 pushed=0은 데이터 0건이라서). - 픽스: 로컬을 UUID로 통일 (근본 해결, 땜질 금지). 14개 서비스 20곳 nanoid() → crypto.randomUUID() 일괄 교체. nanoid 의존성 + electron.vite.config exclude 제거. drizzle schema는 text PK 그대로 유지 (SQLite는 UUID 문자열 저장 가능). ## Bug 5: supabase_realtime publication 누락 - 증상: 로그인 직후 Realtime 채널 상태: TIMED_OUT - 원인: initial_schema.sql이 transcripts 테이블만 publication에 추가. 데스크톱이 구독하는 meetings/history/dictionary는 누락 → postgres_changes 흐르지 않음. - 픽스: 20260411000002_realtime_publication.sql 신규. pg_publication_tables 카탈로그 체크 + 조건부 ADD TABLE (meetings/meeting_memos/ meeting_documents/history/dictionary 5개). supabase db push 적용. ## Bug 6: persistSession:false에서 realtime.setAuth 자동 전파 안 됨 (부분 픽스) - 픽스: CloudSyncService.startRealtime()에 client.realtime.setAuth(access_token) 명시 호출 (채널 구성 이전). - ⚠️ Bug 5+6 적용 후에도 Realtime 여전히 TIMED_OUT. 후속 조사 필요. 블로커 아님 — 주기 pull + Phase 3.3 auto push로 최종 일관성 유지. ## 실증 결과 - A 세션 자동 복원: Restored session for yunchan8804@gmail.com → DB 재오픈 - B push 경로: HistoryService created 56a767ac-... → Sync complete pushed=1 errors=0 - C 로그아웃 복귀: Realtime 종료 → users/_local/d3ro.db 복귀 → local mode - D 재로그인 복원: 실증 A의 restore 경로와 동일, 같은 uuid DB 파일 보존 - E 웹 크로스 디바이스: Phase 3.3 이후로 지연 (Realtime 이슈 별건) 검증: desktop tsc --noEmit ✅, dev 재기동 ✅, push 최초 성공 ✅
This commit is contained in:
parent
c8338ec458
commit
e55687d298
19 changed files with 132 additions and 38 deletions
|
|
@ -208,6 +208,63 @@ OAuth provider도 아직 Supabase에 설정 안 된 상태. 강제 게이트는
|
|||
- `~/Library/Application Support/d3ro-voice/users/${uuid}/d3ro.db` 파일 생성 확인
|
||||
- 기존 `d3ro-voice.db`가 있는 환경에서 archive rename 동작 확인
|
||||
|
||||
### SaaS [9] 로컬 ID UUID 통일 + Realtime publication 픽스 + 실증 A~D 통과 (Phase 5 Part 2, 2026-04-11)
|
||||
|
||||
> **실증 사이클 완주.** Phase 5 Part 1에서 못 잡은 3개 구조 버그를 한 세션에 픽스하고 실증 A/B/C/D 전부 통과.
|
||||
> 로컬→클라우드 push가 **처음으로 성공** (pushed=1 errors=0).
|
||||
|
||||
**실증 결과 (18:36~42)**
|
||||
|
||||
| # | 실증 | 결과 | 증거 |
|
||||
|---|---|---|---|
|
||||
| A | 세션 자동 복원 + RLS 픽스 검증 | ✅ | `Restored session for user: yunchan8804@gmail.com` + `Pull complete applied=0 errors=0` |
|
||||
| B | 사용자별 DB + push 경로 | ✅ | `HistoryService created 56a767ac-0fbc-45c8-98f2-2e2d4afa70f9` + `Sync complete: pushed=1 errors=0` |
|
||||
| C | 로그아웃 → 로컬 복귀 | ✅ | `Realtime 종료` → `Database closed (7da3dd02-...)` → `Reverted to local DB: users/_local/d3ro.db` → `continuing in local mode` |
|
||||
| D | 재로그인 데이터 복원 | ✅ | 실증 A의 "신규 7da3dd02 DB 오픈" 경로가 이 케이스와 동일 — refresh token 기반 자동 복원 |
|
||||
| E | 웹 크로스 디바이스 | ⏭ Phase 3.3 이후 | Realtime TIMED_OUT 때문에 "웹→데스크톱 반영"은 스킵, "데스크톱→웹 조회"만 후속 |
|
||||
|
||||
**발견+픽스한 구조 버그 3건**
|
||||
|
||||
#### Bug 4: 로컬 nanoid PK vs Supabase UUID PK 불일치 (실증 B 첫 시도에서 발견)
|
||||
- 증상: `Push history failed: invalid input syntax for type uuid: "fvy6bIzrEtZ0vo96Dd-WZ"` → `Sync complete: pushed=0 errors=1`
|
||||
- 원인: `apps/desktop/src/main/db/schema.ts`의 모든 PK는 `text('id').primaryKey()` + 서비스 코드는 `nanoid()` 생성 (예: `fvy6bIzr...`, 21자). 반면 Supabase `initial_schema.sql`은 `id uuid PRIMARY KEY DEFAULT gen_random_uuid()`. Phase V2-4 설계 단계에서 "V2-4 MVP는 uuid로 새로 발급, 매핑 테이블 필요"라고 적혀 있었으나 실제 구현에선 매핑이 없어 push 경로 전체가 깨져 있었음. 결과적으로 **빅뱅 사이클 내내 push가 한 번도 성공한 적 없음** (지난 세션 `pushed=0`은 로컬 데이터 0건이라 에러가 안 난 거지 버그가 없어서가 아님).
|
||||
- 사용자 결정: **로컬을 UUID로 통일** (방향 B). 이유 — 3 클라이언트(desktop/web/mobile) id 스펙 일관 + PostgreSQL UUID PK 관용 유지 + `crypto.randomUUID()` 표준 API + `nanoid` 외부 의존성 자체 제거. "땜질 아닌 근본 해결" 사용자 요청.
|
||||
- 픽스:
|
||||
- 14개 서비스 파일에서 `import { nanoid } from 'nanoid'` 제거 + `nanoid()` → `crypto.randomUUID()` 일괄 교체 (총 20곳): HistoryService / MeetingModeService / MemoService / DictionaryService / CaptionService / VoiceConversationService / VoiceCommandService / VoiceActionService / FileTranscriptionService / CustomInstructionService / MeetingDocTemplateService / DictationTemplateService / RAGService / ChainService
|
||||
- `apps/desktop/package.json` dependencies에서 `nanoid: ^5.1.7` 제거
|
||||
- `apps/desktop/electron.vite.config.ts` externalizeDepsPlugin exclude에서 `'nanoid'` 제거
|
||||
- 기존 로컬 DB 2개(`users/_local`, `users/7da3dd02-...`) drop — dev 초기 nanoid row만 있었고 의미 있는 데이터 0건이라 cascade FK 마이그레이션 대신 재생성 선택
|
||||
- drizzle schema(`text('id').primaryKey()`)는 건드릴 필요 없음 — SQLite text PK는 UUID 36자 문자열을 그대로 저장
|
||||
- 검증: 재기동 후 신규 녹음 → `HistoryService created 56a767ac-0fbc-45c8-98f2-2e2d4afa70f9` (UUID v4) → Push → `pushed=1 errors=0` **최초 성공**
|
||||
|
||||
#### Bug 5: Supabase supabase_realtime publication 누락
|
||||
- 증상: 로그인 직후 `Realtime 채널 상태: TIMED_OUT` (실증 A에서 재현, 재기동 후에도 재현)
|
||||
- 원인: `20260409000001_initial_schema.sql:134`이 `ALTER PUBLICATION supabase_realtime ADD TABLE public.transcripts` 한 줄만 실행. 데스크톱 `CloudSyncService.startRealtime()`이 실제 구독하는 `meetings` / `history` / `dictionary`는 publication에 **포함되지 않아** postgres_changes 스트림이 물리적으로 흐를 수 없었음.
|
||||
- 픽스: `20260411000002_realtime_publication.sql` 신규 — `DO $$ ... LOOP ... ALTER PUBLICATION supabase_realtime ADD TABLE public.%I ... END $$;` 패턴으로 `meetings` / `meeting_memos` / `meeting_documents` / `history` / `dictionary` 5개 테이블을 pg_publication_tables 카탈로그 체크 후 조건부 추가. `supabase db push` 적용 완료.
|
||||
|
||||
#### Bug 6: persistSession:false 에서 realtime.setAuth 자동 전파 안 됨 (가설 — 부분 검증)
|
||||
- 증상: Bug 5 migration 적용 후에도 TIMED_OUT 재발.
|
||||
- 원인 (가설): `createClient(url, anonKey, { auth: { persistSession: false, ... } })` 구성에서 `setSession()` 호출이 onAuthStateChange를 통한 `realtime.setAuth()` 자동 전파 경로를 깨뜨리는 것으로 추정. 즉 realtime WebSocket이 anon key JWT로 join을 시도하고 RLS 필터(`user_id=eq.${userId}`)를 통과 못해 TIMED_OUT.
|
||||
- 픽스: `CloudSyncService.startRealtime()`에 `this._client.realtime.setAuth(this._session.access_token)`을 채널 구성 **이전**에 명시 호출 (try/catch로 안전하게). 에러 시 warn만.
|
||||
- 현재 상태: ⚠️ **여전히 TIMED_OUT 재현**. Bug 5 migration은 확실히 적용됐고 setAuth 명시 호출도 들어갔으나 해결 안 됨. 후속 조사 필요 (task 8로 분리). 가능 가설 — (a) Supabase 프로젝트 Realtime 서비스 자체 disabled, (b) setAuth 타이밍 문제(WebSocket 이미 connect된 상태에서 setAuth 무효), (c) RLS 정책이 `user_id = auth.uid()` 외 다른 조건을 요구, (d) realtime 전용 RLS policy(`realtime.messages` 구독 권한)가 별도로 필요.
|
||||
- **블로커 여부**: 아님. 주기적 pull + Phase 3.3 Auto push 경로로 최종 일관성 유지. Realtime은 "웹→데스크톱 5초 이내 반영" 편의 기능.
|
||||
|
||||
**픽스로 해결된 부수 이슈**
|
||||
- `cloud-sync.token` 기반 자동 세션 복원 경로 ✅ (지난 세션 `Invalid Refresh Token: Already Used`는 당시 토큰이 소진된 일회성 이슈, 새 로그인 후 재시작하니 `Restored session for user: yunchan8804@gmail.com` 성공)
|
||||
- `users/${uuid}/d3ro.db` 파일이 로그아웃 시 close만 되고 보존되어 재로그인 시 `created=false`로 복원되는 경로 ✅
|
||||
- LicenseService 로컬 모드 리셋 경로 ✅ (로그아웃 후 익명 로컬 모드 정상 동작)
|
||||
|
||||
**검증**
|
||||
- desktop `tsc --noEmit` ✅
|
||||
- dev 런타임 재기동 ✅ (`Main window shown` → `Restored session` → `DB opened for 7da3dd02` → `Initial sync pushed=0 pulled=0`)
|
||||
- 핫키 녹음 → UUID row 생성 → Push → `pushed=1 errors=0` ✅ (**최초 성공**)
|
||||
- 로그아웃 → Realtime 종료 → `_local` DB 복귀 ✅
|
||||
|
||||
**이 섹션에서 의도적으로 뺀 것**
|
||||
- package-lock.json 정리: `npm install`이 기존부터 `rollup-win32-x64-msvc` optional dep로 실패. 당장 nanoid import는 이미 제거되어 런타임 영향 없음. lockfile은 후속 세션에서 `--omit=optional`로 정리.
|
||||
- Realtime 근본 원인 — task 8 별건으로 분리
|
||||
- 실증 E(웹 크로스 디바이스) — Phase 3.3 이후 수행
|
||||
|
||||
### SaaS [8] OAuth 로그인 완주 + RLS 재귀 픽스 (Phase 5 Part 1, 2026-04-11)
|
||||
|
||||
> **로그인 성공!** 빅뱅 Phase 5 실증 시작. Google OAuth 토큰 교환 성공, 사용자별 DB 생성 확인.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue