Ingridea 앱 로고

기능 개선보다 먼저 확인한 것

Ingridea의 서비스 흐름과 Flutter·iOS·Firebase 구조를 파악한 뒤에도 바로 기능을 추가하지는 않았다. 이 프로젝트에서 먼저 필요했던 것은 다음 세 가지였다.

  1. production을 건드리지 않고 누구나 로컬에서 실행하고 검증할 수 있을 것
  2. 클라이언트가 믿어도 되는 것과 서버만 결정해야 하는 것을 나눌 것
  3. OCR·AI 호출이 늘어도 비용이 무한히 커지지 않게 상한을 둘 것

이 기준으로 첫 P0 작업을 진행했다. 이 글에서 설명하는 변경은 private 저장소에 반영한 상태이며 아직 production에는 배포하지 않았다.

먼저 프로젝트를 읽는 방법부터 통일했다

여러 도구로 한 프로젝트를 읽을 때 위험한 것은 코드가 많다는 사실보다, 같은 코드를 보고도 도구마다 다른 전제를 갖는 것이다. Claude, Cursor, Hermes, Codex가 각자 별도 지침을 읽으면 한쪽에서 고친 규칙이 다른 쪽에는 반영되지 않는다.

그래서 .ai/ 아래에 공통 진입점, 프로젝트 문맥, 컨벤션, 기능지도를 만들었다. 각 AI 도구의 설정에는 긴 설명을 복사하지 않고 이 공통 문서를 참조하게 했다. 기능지도에는 사용자 기능에서 실제 코드, Firestore 경로, 외부 연동, 보안·비용 경계, 검증 방법까지 이어지는 흐름을 적었다.

이 작업은 문서 정리 이상의 의미가 있었다. 예를 들어 “스캔"이라는 한 기능도 iOS VisionKit, Flutter provider, Cloud Function, 외부 OCR·교정 API, Firestore 사용량 문서를 함께 봐야 한다. 지도를 먼저 만들자 어느 경로가 운영 중이고 어느 경로가 레거시인지, 어디까지 확인했고 무엇은 실제 기기 검증이 남았는지를 구분할 수 있었다.

앞으로의 계획도 저장소 안의 오래된 TODO Markdown에 계속 쌓지 않기로 했다. 큰 작업과 구현·검증 단위를 issue로 나누고, 선후관계는 dependency로 관리한다. 문서는 현재 구조를 설명하고, 앞으로 할 일의 상태는 issue가 맡도록 역할을 나눴다.

production 없이도 시작할 수 있는 로컬 환경

다음은 로컬 실행 가능성이었다. 참여 초기에 production Firebase가 있어야 로그인이나 Rules 검증을 할 수 있다면, 작은 확인도 운영 환경 의존 작업이 된다. 반대로 설정만 남은 debug 프로젝트를 무조건 복구하려 하면 실제로 쓰는지도 모르는 환경에 시간을 쓰게 된다.

Docker Compose로 로컬 전용 Firebase 환경을 만들었다. Auth, Firestore, Functions Emulator가 한 환경에서 뜨고 production 자격증명은 컨테이너에 전달하지 않는다. 앱 실행 스크립트도 production 대신 이 Emulator만 바라보도록 했다.

로컬 로그인에서는 이메일 입력란에 아무 값이나 넣고 Continue locally를 누를 수 있다. 실제 이메일을 보내거나 고정 테스트 계정을 관리하지 않고, Auth Emulator에 폐기 가능한 계정을 만드는 방식이다. Emulator 데이터도 영속화하지 않아 다시 띄우면 깨끗한 상태에서 시작한다.

이 환경으로 단순히 앱이 뜨는지만 본 것은 아니다. 인증되지 않은 호출이 거부되는지, 본인 문서는 읽고 다른 사용자의 문서는 읽지 못하는지, callable Function이 로컬에서 응답하는지를 smoke test로 확인했다. 로컬 환경의 목적을 “편하게 실행"이 아니라 “production과 분리된 검증"으로 잡았다.

별도의 비운영 환경은 접근성과 사용 여부가 확인되지 않아 P0 검증 범위에서 제외했다. 대신 production의 읽기 전용 기준선과 로컬 Emulator를 사용했다.

클라이언트가 결정하던 값을 서버로 돌려보내기

Firestore 접근 경계

데이터 접근 정책을 인증과 소유권 원칙에 맞춰 다시 검토하고, 민감한 상태는 서버 검증 범위로 정리했다. 구체적인 변경 전후 규칙은 production 반영 후 다루기로 했다.

결제와 구독

결제의 신뢰 경계도 서버 검증 중심으로 재정비하고 불필요한 처리와 기록 경로를 줄였다. 같은 검증 결과가 반복될 때 불필요한 Firestore write를 생략해 재시도에도 일관된 결과를 유지한다. 변경 전 구조와 내부 구현은 production 반영 후 다루기로 했다.

외부 AI 호출을 서버 경계로 모으기

외부 OCR·AI 연동의 인증과 사용량 통제를 서버 경계로 통합했다. 기존 호출 경로와 자격 증명 배치 방식은 production 반영 전에는 공개하지 않는다.

이 변경으로 비밀의 위치뿐 아니라 요청을 통제할 위치도 서버 한 곳으로 모였다. 다만 “서버로 옮겼으니 끝"은 아니었다. 이미지와 AI 호출은 요청 하나의 크기와 횟수에 따라 비용이 달라지기 때문에, 같은 Function에 비용 경계도 함께 넣었다.

비용은 나중에 보는 지표가 아니라 입력 조건

Ingridea에서는 비용을 가장 먼저 확인할 기술 제약으로 정했다. 요청 크기, 사용자별·서비스 전체 호출량, 실행 시간과 동시 처리 자원에 단계별 상한을 설계했다. 정확한 제한값은 production 배포와 실측을 마친 뒤 공개하기로 했다.

현재 가정으로 월 3,000회 스캔의 vendor 비용은 약 18~36달러다. 설정한 전체 상한을 계속 사용하더라도 vendor 비용이 월 수백 달러 안에서 제한되도록 설계했다. Cloud Functions compute와 egress 비용은 이 계산에 포함되지 않는다.

이 숫자는 실제 청구액이 아니다. 모델이 소비하는 실제 토큰과 이미지별 지연은 배포 후 측정해야 한다. 그래도 불확실하다는 이유로 상한을 두지 않는 것과, 가정을 공개하고 최악의 호출량을 코드로 막는 것은 다르다. 우선 폭주 가능한 범위를 제한하고 실측 뒤 단가와 quota를 다시 조정하기로 했다.

어디까지 확인했나

P0 브랜치에서는 다음 검증을 통과했다.

  • Functions TypeScript build 성공
  • Functions unit test 10/10 통과
  • Docker Emulator Auth·Rules·callable smoke test 통과
  • Flutter analyze error 0건 확인(기존 warning/info 434건은 남아 있음)
  • Flutter test 55/55 통과
  • Flutter source와 패키지 설정에서 외부 OCR·AI 직접 호출용 자격 증명과 설정 참조 제거 확인

하지만 이 결과가 production 배포 준비 완료를 뜻하지는 않는다. 아직 다음 작업이 남아 있다.

  • StoreKit Sandbox에서 실제 구매와 복원 검증
  • 실제 OCR 이미지 1장·2장의 품질과 지연 측정
  • release artifact에 대한 secret scan
  • production diff와 rollback 계획 승인
  • 승인된 배포 후 오류·지연·비용 관찰

더 중요한 제약도 있다. 저장소 선언과 운영 상태에 차이가 있어 사전 diff와 rollback 승인 없이 배포하면 안 된다. 현재는 production에 반영하지 않은 상태다.

이번 P0에서는 production을 배포하지 않았고 관련 issue도 닫지 않았다. private 저장소에서 위험한 경로를 정비하고 로컬에서 검증 가능한 상태를 만든 것과, 운영 반영을 완료한 것은 분명히 다른 단계이기 때문이다.

기능보다 먼저 만든 것

이번 작업에서 새 사용자 기능은 거의 늘지 않았다. 대신 새 프로젝트를 계속 다룰 수 있는 바닥을 만들었다.

  • 여러 도구가 같은 프로젝트 지도와 규칙을 읽는다.
  • production 없이 로그인, Rules, Function 경계를 로컬에서 검증할 수 있다.
  • 사용자 소유권과 구독 상태의 결정권을 인증된 서버 경계에 둔다.
  • OCR·AI 호출의 비밀과 비용 상한을 서버에서 통제한다.
  • 확인한 것과 아직 production에서 확인하지 않은 것을 문서와 issue로 나눈다.

낯선 프로젝트의 개발에 참여할 때 가장 먼저 필요한 것은 코드를 많이 바꾸는 속도가 아니었다. 어디서 안전하게 실행할 수 있는지, 무엇을 신뢰할 수 있는지, 실패했을 때 비용이 어디까지 커질 수 있는지를 먼저 정하는 일이었다. 다음 작업부터는 이 경계 안에서 실제 기기와 Sandbox 검증을 이어갈 예정이다.