diff --git a/docs/superpowers/specs/2026-04-25-dogfood-feedback.md b/docs/superpowers/specs/2026-04-25-dogfood-feedback.md index 3f8c75c..2f3f343 100644 --- a/docs/superpowers/specs/2026-04-25-dogfood-feedback.md +++ b/docs/superpowers/specs/2026-04-25-dogfood-feedback.md @@ -577,6 +577,226 @@ inkling_export_version: 1 --- +## F6. 메모 데이터 백업 + 복원 (3-layer 권장) (🌱 raw) + +**발견:** 2026-04-26 dogfood 시작 직전 사고 실험. 슬라이스 v0.4 의 메모 데이터는 `%APPDATA%\Inkling\Inkling\profiles\default\` 단 한 위치에만 존재. 디스크 고장·실수 삭제·DB 손상·OS 재설치 = 총 손실. Strategy.md §1 의 "이제 잊어도 됩니다" 보상이 **데이터 영속성 신뢰** 위에 서 있어서, 이 신뢰가 깨지면 슬라이스 §1.3 의 종료 조건 ("본인 2주 dogfood 완주") 자체가 위협받음. + +### 관찰 + +현재 단일 실패 지점 (SPOF): +- `inkling.sqlite` (WAL 두 파일 포함) — 노트·태그·AI 메타·intent 전부 +- `media/` — 클립보드 이미지 바이너리 (DB 의 `rel_path` 와 짝) +- 부팅 시 `MediaGc` 가 DB 미참조 미디어를 정리 — DB 가 손상되면 미디어도 GC 사이클에서 사라질 수 있음 (위험 증폭) + +기존 부분 완화는 0: +- 자동 백업 0 +- 외부 동기화 0 +- import 경로 0 +- F5 (export) 가 promoted 되어도 단방향 + 수동 + +본인 dogfood 운영 환경 신호: +- 이미 `gitea.altair823.xyz` 자체 호스팅 중 — 사적 git remote 인프라가 있음 +- 프로젝트 메모리: Mac=업무 / Windows=개인+dogfood — 디바이스 전환 가능성 (단일 활성, 동시 X) +- RTX 4070 Windows = 메인 dogfood 머신, 디스크 1대 SSD 가정 → 디스크 고장 1회 = 전체 손실 + +### 제안 방향 + +**3-layer 다층 백업.** 각 layer 가 다른 위협 모델을 커버. + +| Layer | 위협 모델 | 비용 | 슬라이스 적합 | +|-------|----------|------|--------------| +| **L1 로컬 원자 스냅샷** | 실수 삭제, DB 손상, AI 마이그레이션 실패 | 작음 | ✅ 슬라이스 후속 가벼움 | +| **L2 git remote 마크다운 동기화** | 디스크 고장, 디바이스 이동, 버전 이력 필요 | 중 | 🔬 별도 미니 spec | +| **L3 전체 export/import** | OS 재설치, 디바이스 이주, 사용자 통제 백업 | 작음 (L1 + F5 위) | ✅ F5 위에 import 만 추가 | + +#### L1 — 로컬 원자 스냅샷 + +`better-sqlite3` 의 `db.backup(path)` API 사용. WAL 활성 상태에서도 안전한 원자적 복제 (파일 단순 cp 와 다름 — WAL 미반영분 누락 위험 없음). + +```ts +// 의사코드 +async function snapshot(): Promise { + const ts = format(new Date(), 'yyyy-MM-dd'); + const dest = join(profileDir, 'backups', `inkling-${ts}.sqlite`); + await db.backup(dest); + await rotate({ daily: 14, weekly: 4, monthly: 6 }); +} +``` + +**스케줄**: +- 앱 종료 직전 1회 (`before-quit`) +- 매일 첫 캡처 시 (`/backups/.last-snapshot` mtime 비교) +- 명시 트리거: 트레이 메뉴 "지금 백업" + +**저장 정책 — Grandfather-Father-Son**: +- 일일 14개 → 주간 4개 → 월간 6개. 누적 24개 안팎, 평균 사이즈 가정 시 < 50MB. +- `backups/` 는 미디어 미포함 (DB 만). 미디어는 L2 또는 L3 책임. + +**위협 미커버**: 디스크 자체 고장. SSD 가 죽으면 backups/ 도 같이 죽음. L2 가 이 위협 담당. + +#### L2 — git remote 동기화 (RECOMMENDED 핵심 layer) + +**핵심 결정: SQLite 바이너리를 push 하지 말고, F5 마크다운 트리를 push 한다.** + +| | SQLite 바이너리 | F5 마크다운 트리 | +|--|----------------|-----------------| +| diff 의미성 | 0 (전체 blob 변경) | ✅ 노트별 라인 diff | +| repo 사이즈 | 매 push 마다 풀 DB | 변경 노트만 | +| 멀티 디바이스 머지 | 불가 (binary conflict) | 가능 (텍스트 merge) | +| 외부 도구 호환 | 0 | RAG / Obsidian / grep 즉시 | +| F5 와 의 시너지 | 0 | F5 그대로 재사용 | + +→ **F5 의 export 형식을 git 추적 대상으로 그대로 사용**. F5 가 promoted 되면 F6-L2 는 그 위에 자동화 layer 만 얹는 구조. + +**아키텍처**: + +``` +[CaptureService / NoteRepository] + │ (write) + ▼ + inkling.sqlite ← Layer 0 (primary) + │ + │ (DB write 후 dirty 마크) + ▼ + /sync/ ← Git working tree (L2) + ├── notes/ ← F5 형식 마크다운 + ├── media/ + ├── index.jsonl + └── manifest.json + │ + ▼ (BackgroundSyncWorker, 5분 주기 또는 dirty=true 후 30초 debounce) + git add . && git commit -m "..." && git push +``` + +**커밋 메시지 컨벤션** (자동 생성): + +``` +chore(notes): +3 ~1 -0 (2026-04-26T14:23+09:00) + +added: 01H89aab... 주간 회고 PR 리뷰 +added: 01H89bcd... ... +modified: 01H78xyz... 어제 회의 메모 +``` + +기존 inkling 본 저장소 commit 스타일과 분리되며, "automated note sync" 임이 명확. + +**Auth & 보안**: +- Personal Access Token 또는 SSH key. Electron `safeStorage` API (OS keychain 백엔드 — Windows 는 DPAPI) 로 평문 미저장. +- 토큰은 절대 로그/오류 메시지에 노출 금지 (slice §1.1 invariant 4 확장). +- repo 는 **반드시 private** — 평문 raw_text 노출 위험. 처음 설정 시 다이얼로그에 굵은 경고. + +**Conflict 정책 — single-active-device 가정**: +- push 가 거부되면 (다른 디바이스가 먼저 push) → `git pull --rebase` → 자동 머지 시도 +- 머지 실패 (같은 노트 양쪽 수정) → 트레이 알림 + 수동 해결 다이얼로그. 노트별 "내 버전 / 원격 버전 / 둘 다 보존" 3-way 선택 +- 본인 dogfood = 단일 활성 디바이스라 거의 발생 안 함 — 멀티 디바이스 시나리오 정식 지원은 L2 의 v2 + +**Repo 초기화**: +- 첫 설정 시 사용자가 빈 remote URL 입력 → 앱이 `git init` + 초기 export + 첫 커밋 + push +- 또는 기존 repo URL 입력 → clone → 검증 (이전 manifest 호환성) → 동기화 시작 + +**미디어 정책**: +- 평문 push 가 default — 텍스트 노트와 함께 미디어도 git 에 올라감 +- repo 사이즈 폭발 위험 → 토큰 옵션: "이미지 제외" 토글 또는 Git LFS (선택). 1차는 옵션 X, 단순 push, 사이즈 모니터링만. +- 이미지 제외 시 frontmatter 의 `images` 항목은 보존하되 파일은 미포함 → 복원 시 placeholder 표시 + +#### L3 — 수동 전체 export / import + +- **export**: F5 가 그대로 담당. 변경 없음. +- **import**: 신규. F5 형식 폴더를 읽고 DB 에 upsert. 충돌 정책: + - id 충돌 + 본문 동일 → skip + - id 충돌 + 본문 상이 → 사용자 선택 (덮어쓰기 / skip / 양쪽 보존하며 새 id 생성) + - id 신규 → insert + - 미디어 → MediaStore 에 복사 +- 트레이 메뉴 "백업에서 복원..." → 폴더 선택 → 미리보기 (n개 신규, m개 변경, k개 충돌) → 확인 → 적용 + +### 결정 대기 + +1. **3-layer 동시 도입 vs 단계적**: L1 → L3 → L2 순서가 비용·위험 단조 증가라 권장. L1 만으로도 SPOF 완화의 80% 커버. +2. **L2 sync 단위**: 매 변경 vs 5분 debounce vs 종료 시 1회 vs 명시 동기화만. 실시간일수록 데이터 손실 윈도우 작지만 git push 빈도 폭발 + 네트워크 마찰. **5분 debounce + 종료 시 즉시 push** 가 1차 권장. +3. **L2 repo 분리**: 기존 `gitea.altair823.xyz/altair823-org/inkling` (소스 코드) 와 분리된 별 repo (예: `altair823-org/inkling-data`) — **반드시 분리**. 데이터·코드 라이프사이클 다름, 외부 협업자에게 데이터 노출 위험. +4. **L2 충돌 시 정책 — slice §1.1 vs 사용자 선택**: 자동 "내 디바이스 우선" 가속 vs 매번 묻기. dogfood 단일 디바이스 가정으론 자동 OK, but defensive 차원에서 충돌 발생 시 1회 확인이 안전. +5. **media 의 git 추적**: 포함 vs 제외 vs LFS. 1차는 포함 + 사이즈 < 100MB 경고. 누적 시점에 후속 결정. +6. **L1 백업 위치**: `/backups/` (현 프로필 안) vs 별 디렉터리 (`%APPDATA%\Inkling\backups\`) vs 사용자 지정 외부 경로. 외부 경로 옵션이 OneDrive 등 클라우드 sync 폴더 이용 가능 — 거의 공짜 cloud backup. +7. **import 시 raw_text invariant 보호**: slice §1.1 "raw_text 불변" 은 *동일 id 내* 의미. import 가 같은 id 의 raw_text 를 다른 값으로 덮어쓰면 invariant 위반. 충돌 시 raw_text 다르면 **새 id 강제** 정책이 안전. +8. **L2 첫 설정의 UX 부담**: token 입력 + remote 검증 + 초기 push 가 dogfood 1일차 첫 인상에 마찰. 첫 설치 후 N 일 (예: 7일) 까지는 L1 만 켜두고 L2 는 트레이 메뉴 "원격 백업 설정" 으로 opt-in 권장. +9. **암호화 — local-first 라도 token 외 추가 보호 필요한가**: SQLite·미디어·git 모두 평문. 디스크 도난 시 노출. 1차는 평문 (slice §1.1 미적용 영역), 후속에 SQLCipher / age 암호화 검토. +10. **slice §7 strict-pin invariant 영향**: L1 은 `better-sqlite3.backup()` 만 사용 — 추가 dep 0. L2 는 `simple-git` 또는 `nodegit` 같은 git 바인딩 또는 child_process 로 git CLI 호출. CLI 호출이 dep 0 + 사용자 git 환경 재사용. **CLI 호출 권장**. + +### 가설·측정 + +| # | 가설 | 측정 | +|---|------|------| +| H1 | dogfood 2주 누적 동안 디스크 측 사건 (실수 삭제, DB 손상, 디스크 고장) ≥ 1회 발생할 정성 가능성 | 발생 시 라벨링 | +| H2 | L1 단독 만 도입해도 SPOF 발생 시 회복 가능 (백업으로 ≥ 95% 데이터 복원) | 복원 시뮬레이션 1회 (의도적 DB 삭제 후 복원) | +| H3 | L2 5분 debounce push 가 일평균 ≤ 30 commit. repo 사이즈 누적 < 100MB / 1년 | 로그 측정 | +| H4 | L2 commit 메시지 통계 (added·modified·deleted) 가 dogfood 활동 회고 자료로 가치 발생 | 정성 평가 | +| H5 | "이제 잊어도 됩니다" 보상의 신뢰도 — 백업이 있다는 인지가 capture 빈도 또는 심리적 부담 감소에 영향 | 본인 self-report | + +### 범위 + +- **In (L1 — 슬라이스 후속 가벼움):** + - `BackupService` 신규 — `db.backup()` 래핑 + 로테이션 + - 트레이 메뉴 "지금 백업" + 타임스탬프 표시 + - 종료·일일 1회 자동 트리거 + - `backups/` 디렉터리 — `.gitignore` 와 같은 .ignored 마커 고려 + - 단위 테스트 — 로테이션 GFS 정책 +- **In (L3 — F5 위에 import 만):** + - `ImportService` 신규 + - 충돌 미리보기 다이얼로그 + - 트레이 메뉴 "백업에서 복원..." +- **In (L2 — 별 spec, 가장 큼):** + - `SyncService` (BackgroundSyncWorker) + - F5 ExportService 의 incremental 모드 (변경 노트만) + - git CLI 래퍼 + safeStorage 토큰 관리 + - 설정 UI — remote URL, 토큰, 동기화 주기, 미디어 포함 여부, 충돌 정책 + - 충돌 해결 다이얼로그 + - 상태 표시 (트레이 아이콘 색·tooltip) +- **Out:** + - SQLCipher 암호화 + - 다중 활성 디바이스 실시간 sync + - 외부 SaaS (Dropbox API, Google Drive API) 직접 연동 + - Rsync 전송 + - SQLite WAL 의 logical replication + +### 영향 + +- **Schema:** 없음 +- **신규 파일 (L1 + L3):** + - `src/main/services/BackupService.ts` + - `src/main/services/ImportService.ts` + - `src/main/ipc/backupApi.ts` + - 테스트 `tests/unit/BackupService.spec.ts`, `ImportService.spec.ts` +- **신규 파일 (L2 별 spec):** + - `src/main/services/SyncService.ts` + - `src/main/services/GitClient.ts` (git CLI 래퍼) + - `src/main/services/CredentialStore.ts` (safeStorage 래퍼) + - 설정 UI (Settings 창 신설 — 슬라이스 §5 의 "Settings 창 없음" 결정 재검토 필요) +- **외부 의존:** + - L1: 0 + - L3: 0 + - L2: 사용자 머신의 git CLI 필요. README 사전 요구 항목 추가 +- **로깅:** + - 백업 시작·완료·사이즈만. 본문·파일명 미기록 + - 동기화 push 결과·conflict 발생만. 토큰·URL 일부 마스킹 +- **문서:** + - 본 항목 promoted 시 분리 권장: + - `2026-04-26-local-snapshot.md` (L1) + - `2026-04-26-import.md` (L3, F5 와 자매) + - `2026-04-26-git-sync.md` (L2) + - 또는 단일 `2026-04-26-backup-strategy.md` 로 통합 후 §A·§B·§C 로 분리 + +### 비고 + +본 항목과 F5 는 **완벽한 데이터 라이프사이클 그림** 의 두 절반: +- F5 = 외부 회수 (read 방향) +- F6 = 외부 백업 + 내부 복원 (write·sync 방향) + +L2 (git sync) 가 dogfood 본인의 기존 인프라 (gitea 자체 호스팅) 와 자연스럽게 맞물리는 점은 본 사용자에게 특히 강한 가치. 다른 사용자였다면 GitHub Actions 등 외부 서비스 의존이라 우선순위 낮을 수 있음. + +slice §1.3 종료 조건 ("크래시 0회") 와 별개로, **"데이터 손실 0회"** 가 silent invariant 로 추가되어야 함. 본 항목 → `slice spec §1.3` 추가 갱신 후보. + +--- + ## (다음 항목 자리) -새 피드백 추가 시 `## F6. 짧은 제목 (🌱 raw)` 헤더로 시작. 표준 슬롯 6개 채우거나 비워둔 채 시작 가능. +새 피드백 추가 시 `## F7. 짧은 제목 (🌱 raw)` 헤더로 시작. 표준 슬롯 6개 채우거나 비워둔 채 시작 가능.