← 목록
기타 2026-07-29 6KB 읽기 7분

크라우니 오피스 → 크라우니클라우드(9611) 선택적 미러 (P2-⑩)

개요

crowny-ai(:9852) 오피스 문서(cdf) 저장을 기존 로컬 저장(data/office)은 그대로 유지하면서 크라우니클라우드(정본 9611)에 추가 best-effort 미러만 배선했다. 저장위치 이전·충돌UX 재설계는 하지 않았다(데이터 손실 위험 회피, 사용자 확정 방침).

무엇을 했는지

  1. owner 변환 — crowny-ai username(3~24자, [a-z0-9._-])은 9611의 owner 게이트(merge/store 8~100자, blob 1~100자, 둘 다 [A-Za-z0-9_-]만·점 미허용)를 못 채울 수 있어 "office_" 접두 + 비허용문자 치환으로 변환(cloudOwnerId()). 항상 10~31자, 안전 문자집합.
  2. 저장 미러/api/office/save에서 로컬 rename 확정 후(정본 먼저) best-effort로:
- cdf 바이트 → POST 127.0.0.1:9611/api/blob/upload/<owner>{id(sha256),size,dedup} - 문서 메타 → POST 127.0.0.1:9611/api/merge (key=office-docindex, item={id:docId, ts:updatedAt, docId, title, sha256, size, version, updatedAt}) - 9611 merge는 실제 LWW 병합기임을 실측 확인(POST가 상태를 통째로 덮지 않고 서버가 id/ts로 기존과 병합) → GET-병합-POST 왕복 없이 항목 1개만 POST해도 인덱스 upsert됨. - 로컬 응답은 최대 CLOUD_MIRROR_BUDGET_MS(4000ms, 개별호출 상한 3500ms)까지만 미러를 기다리고 cloudMirror:true/false(+cloudMirrorReason)를 응답에 포함. 예산 초과/9611 다운이어도 로컬 저장은 이미 확정돼 200/201 유지.
  1. 선택 보조 조회GET /api/office/cloud-list(9611 docindex 조회), GET /api/office/cloud-get?docId=(인덱스에서 sha256 찾아 blob 회수). 로컬 list/get과 별개 경로.
  2. 한선씨 동반오피스클라우드미러.한선: owner변환/규격판정/sha유효/미러4상종합(티=전체성공/타=단계실패/옴=예산초과)/응답필드판정. 자가검증 8케이스 전부 PASS.

관련 파일

  • /Users/ef/crowny-ai/server.js/api/office/save 핸들러 + 헬퍼(cloudOwnerId, cloudRequest, cloudBlobUpload, cloudDocindexUpsert, officeMirrorAttempt, officeMirrorToCloud, cloudDocindexList, cloudBlobDownloadRaw) + /api/office/cloud-list, /api/office/cloud-get 신규 라우트.
  • /Users/ef/crowny-ai/server.js.bak-오피스미러 — 수정 전 백업.
  • /Users/ef/crowny-ai/오피스클라우드미러.한선 — 한선씨 동반(판정 코어, 컴파일·자가검증 PASS).
  • /Users/ef/crowny-ai/CLAUDE.md — API 표 갱신(cloudMirror 필드, cloud-list/cloud-get 라우트) + 섹션 추가.
  • 9611 실체(참고, 무수정): /Users/ef/crowny-services/서비스서버.한선(실LWW merge처리 1104~1293행), /Users/ef/crowny-services/블롭스트림.한선(blob 디스패처 617~704행).

crowny-ai 재기동

launchctl kickstart -k gui/$(id -u)/com.crowny.ai (LaunchAgent com.crowny.ai, KeepAlive). 재기동 전후 health 200 확인(전: 200, 재기동 10:19:08, 후: 200, /tmp/crowny-ai.err 신규 에러 없음 — 파일 mtime 7/26 그대로).

검증 (실제 경로 — 게이트웨이/프록시 경유)

  1. crowny-ai health: curl 127.0.0.1:9852/api/health → 200.
  2. e2e: officemirtest2 계정 신규가입 → /api/office/save(42B cdf) → {"ok":true,...,"cloudMirror":true}. curl 127.0.0.1:9852/api/cloud/merge?key=office-docindex -H "X-Crowny-Owner: office_officemirtest2"(same-origin 프록시)와 curl https://cloud.crowny.org/api/merge?...(공개 게이트웨이) 양쪽에서 동일 docId/sha256 확인. 해당 sha256으로 /api/cloud/blob/<owner>/<sha256>(프록시)·https://cloud.crowny.org/api/blob/...(게이트웨이) 둘 다 원본과 byte-identical(cmp PASS).
  3. 500KB(512000B) cdf 저장 → cloudMirror:true, blob 다운로드 cmp byte-identical PASS. merge 메타는 여전히 소형(항목 1개 ~200B).
  4. 실패무해: 로컬 서버(server.js) 안의 동일 로직을 격리 하네스로 복제해 (a) 연결거부(ECONNREFUSED, 존재 않는 19999포트) (b) hang/타임아웃(리스닝만 하고 무응답) 두 경우 모두 테스트 — 둘 다 localSaveOk:true 유지, cloudMirror:false로 5ms~1507ms 내 정상 resolve(무한블록 없음, throw 없음). 9611 자체는 라이브 상태를 건드리지 않고 검증(별도 임시 포트로 격리).
  5. 회귀: officemirtest2 계정으로 기존 /api/office/list·/api/office/get·/api/office/delete 정상. baseVersion=0(구버전)으로 재저장 시도 → 409 conflict, serverVersion:1 정상 유지.
  6. cloud-list/cloud-get 왕복 확인 — 인덱스 조회 후 사라진 로컬 문서(delete됨)도 클라우드 쪽엔 남아있음을 확인(→ 잔여 이슈로 기록).
  7. 9611 자체 프로세스(pid, 서비스서버.toau, 크라우니클라우드/cloud.crowny.org 등 다수 도메인이 공유)는 무접촉. quota 프록시 200으로 계속 정상 확인.

잔여 이슈

  • cloud 쪽 tombstone 미구현 — 로컬 /api/office/delete가 클라우드 docindex/blob에 전파되지 않음(9611 merge는 deleted 배열을 지원하므로 후속 작업으로 연결 가능, 이번 범위는 저장 미러 중심이라 보류).
  • blob GC 없음 — 9611 blob은 dedup(sha256 기준)만 하고 삭제 API가 없어(설계상 append-only) 문서 버전이 바뀔 때마다 새 sha256 blob이 계속 쌓인다. 장기적으로 9611 쿼터(quota API로 조회 가능)에 영향 — 별도 GC/보존정책 필요.
  • cloud-get 규모 한계 미검증 — 문서 수가 매우 많아진 사용자의 docindex 조회 성능/merge 파일 크기(4MB 상한)는 이번 범위에서 스트레스 테스트하지 않음.
  • owner 매핑 비가역office_<username치환> 변환은 결정론적이라 재계산 가능하지만, 9611 쪽에서 원래 crowny-ai username을 되짚어 알 방법은 없음(요구되지 않았음).