Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/launch-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,29 @@
- [ ] pmlegal 문서의 앱 내 링크·시행일 최종 확인
- [ ] 파일럿 QR 인쇄물은 그대로 유효(주소가 `go.pawmate.kr` 라 백엔드 교체 무관)
- [ ] iOS `aps-environment` 가 릴리스 빌드에서 production 인지 확인(푸시)

## 6. 베타 트랙 정의 — 업체 파일럿과 개인 사용자 베타는 별개

측정하려는 값이 서로 다른 두 트랙이며, **업체 파일럿만 돌리면 신원 인증 계열
표본은 하나도 안 모인다**(사장님은 펫 등록도, 사진 게이트 게시글 작성도 안 함).

| 트랙 | 대상 | 검증 대상 | 문서 |
|---|---|---|---|
| 업체 파일럿 (P1) | 미용업체 5~10곳 | 케어 리포트 발행·열람 흐름 | pilot-onboarding-checklist.md |
| 개인 사용자 베타 | 클로즈드(섭외 업체 지인·신원 확인된 테스터) | 펫 신원 등록(frames_from_video 섀도), 게시글 사진 인증(0.63), video_too_large 발생률 | 본 문서 §7 |

수용 리스크(결정, 사고 아님): 클로즈드 베타 동안 frames_from_video 는 섀도
모드라 프레임 바꿔치기 구멍이 열린 채 돌아간다 — 테스터가 신원 확인된
범위라 노출 대비 측정 가치가 크다고 판단(pmdb #135 에 기록). **오픈 베타로
넓히기 전에 enforce 전환 여부를 재검토할 것.**

## 7. 베타 종료 조건 (정식 출시 전 확인)

실행 항목(§1~5)과 별개로, 아래가 확인돼야 정식 출시로 넘어간다.

- [ ] (베타 **진입** 조건) 크래시 리포팅 + Edge Function 실패 가시화
(`ai_unavailable`·`video_too_large`·`internal_error` 알림) — pmdart #157.
실패가 안 보이는 상태로 데이터 수집 베타를 시작하면 목적의 절반이 사라진다.
- [ ] frames_from_video 표본 **30건** 도달 → 오탐률 확인 → enforce 전환 판단 (pmdb #135)
- [ ] `IDENTITY_PASS_THRESHOLD 0.63` 근거 확보 — post 인증 로그의 identity_score 분포로 통과선 재검토
- [ ] `video_too_large` 발생률·기기 분포 확인 → 클라 압축 / Files API 판단 (pmdb #136)
16 changes: 8 additions & 8 deletions docs/supabase-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
| `reset-password` | 전화 OTP 인증 후 비밀번호 재설정 | 없음 (선행 전화 인증 필요) | false | ACTIVE v3 |
| `verify-location` | GPS 현장 동네(행정동) 인증 | 커스텀 JWT Bearer | false | ACTIVE v10 |
| `verify-post-photo` | 게시글 사진 동일개체 매칭 + 라이브니스(Gemini) | 커스텀 JWT Bearer | false | ACTIVE v9 |
| `enroll-pet-identity` | 펫 신원 등록 (임무 영상 → AI 검증 → 기준 프레임) | 커스텀 JWT Bearer | false | ACTIVE v8 |
| `enroll-pet-identity` | 펫 신원 등록 (인증 영상 → AI 검증(+프레임 출처) → 기준 프레임) | 커스텀 JWT Bearer | false | ACTIVE v8 |
| `search-petcafe` | 애견카페 실시간 검색 (네이버 지역검색 프록시) | 커스텀 JWT Bearer | false | ACTIVE v9 |
| `resolve-region` | 좌표 → 행정동 역지오코딩 (부수효과 없음) | 커스텀 JWT Bearer | false | ACTIVE v3 |
| `invite-guardian` | 공동보호자 초대 (가입자: 인앱 알림 / 미가입: 초대 SMS) | 커스텀 JWT Bearer | false | ACTIVE v1 |
Expand Down Expand Up @@ -252,21 +252,21 @@
- 401 `unauthorized`, 403 `forbidden`(보호자 아님), 500 `server_misconfigured` / `internal_error`
- **내부 로직**: ⓪ `pet_guardians`에서 uid가 petId의 보호자인지 확인 ① `users`의 활동지역 인증 상태 확인 — `is_location_verified`, `last_verified_at`(30일 경과 시 만료 취급), `region_code` ② 모의위치 거절(+`record_photo_verification` 실패 기록) ③ 촬영 좌표를 네이버 역지오코딩 → 행정동 코드가 `users.region_code`와 일치해야 함 ④ `pet_identity_frames`에서 기준 프레임 목록 조회 + Storage `media` 버킷에서 다운로드(없으면 `pet_not_enrolled`) ⑤ **Gemini 2.5 Pro**(구조화 JSON 출력, temperature 0, 429 시 최대 2회 backoff 재시도)로 기준 프레임 N장 + 게시 사진 1장 → 동일 개체 `identity_score` + 라이브니스(`is_real`, dog/cat_real/fake) 판정 ⑥ `is_real && real>fake`(라이브니스) AND `identity_score >= 0.63`(동일 개체) 통과 시 → 사진을 `media/<uid>/posts/<ts>.jpg`로 업로드 → `record_photo_verification` RPC(p_result='pass', p_ttl_min=15, p_pet_id, p_match_score, p_matched=true 등)가 **검증 토큰** 반환(게시글 작성 시 제출). 실패 경로 중 `mock_location`/`geocode_failed`/`region_mismatch`/`not_real_pet`/`identity_mismatch`는 RPC로 실패 기록을 남기지만, `not_verified`/`pet_not_enrolled`/`ai_unavailable`은 기록 없이 반환한다.
- **시크릿**: `JWT_SECRET`, `NAVER_MAP_KEY_ID`, `NAVER_MAP_KEY`, `GEMINI_API_KEY`, `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, (`ALLOW_ORIGIN`)
- **정책**: 지역 재인증 주기 30일(`REVERIFY_DAYS`, 위치기반서비스 이용약관 제7조와 일치), 검증 토큰 TTL 15분, 라이브니스 하한 0.70(상수 `AI_REAL_THRESHOLD` — 실제 판정은 `is_real && real>fake` 사용), 동일 개체 통과선 0.63.
- **정책**: 지역 재인증 주기 30일(`REVERIFY_DAYS`, 위치기반서비스 이용약관 제7조와 일치), 검증 토큰 TTL 15분, 라이브니스는 `is_real && real>fake`(사진 1장 판정은 저조도·역광·블러에 민감해 절대 하한 없음 — 도입은 오탐률 측정 후에만), 동일 개체 통과선 0.63. **신뢰 경계 주의**: `lat`/`lng`/`accuracy`/`isMocked`는 클라이언트 자기신고 값이라 직접 POST로 위조 가능 — 서버 권위 판정은 Gemini 이미지 판별뿐이며, 기기 증명(App Attest/Play Integrity)·좌표 타당성 교차검증은 출시 전 과제.

### enroll-pet-identity

- **엔드포인트**: `POST /functions/v1/enroll-pet-identity` — verify_jwt=false(수동 검증)
- **인증**: **커스텀 JWT Bearer**.
- **요청 바디**: `{ petId: string(필수), challenge: string[](필수, 1개 이상. 알려진 코드: pat_head|hold_paw|scratch_chin|stroke_back|hand_in_frame — 단 서버는 목록을 강제하지 않으며 미지 코드는 문자열 그대로 Gemini 프롬프트에 들어감), videoBase64: string(필수), videoMime?: string(기본 'video/mp4'), frames: string[](필수, base64 3장 이상), mimeType?: string(기본 'image/jpeg') }`
- **요청 바디**: `{ petId: string(필수), videoBase64: string(필수), videoMime?: string(기본 'video/mp4'), frames: string[](필수, base64 3장 이상), mimeType?: string(기본 'image/jpeg') }` — 동작 미션(challenge)은 AI 판별 오탐이 커서 제거됨(스푸핑 차단은 실물·라이브 판별로 유지).
- **응답**:
- 200 성공 `{ enrolled: true, species, breed, colors, frameCount, frames: [url...], infoMatch: { species_kind, breed, color, warnings }, warnings, challengePassed: true }`
- 400 `{ enrolled: false, reason: "missing_pet" | "no_video" | "too_few_frames" | "no_challenge" }` / `invalid_json`
- 200 실패 `{ enrolled: false, reason: "ai_unavailable"(+detail/videoKb 진단) | "not_real_pet"(+ai) | "not_consistent_pet"(+ai) | "challenge_failed"(+missing, ai) }`
- 200 성공 `{ enrolled: true, species, breed, colors, frameCount, frames: [url...], infoMatch: { species_kind, breed, color, warnings }, warnings }`
- 400 `{ enrolled: false, reason: "missing_pet" | "no_video" | "too_few_frames" | "video_too_large" }` / `invalid_json`
- 200 실패 `{ enrolled: false, reason: "ai_unavailable" | "not_real_pet"(+ai) | "not_consistent_pet"(+ai) | "species_mismatch"(+ai) }` — `frames_not_from_video`는 **섀도 모드**라 거절 사유로 반환되지 않음(`FRAMES_FROM_VIDEO_ENFORCE=true` 전환 시 활성화)
- 401 `unauthorized`, 403 `{ enrolled:false, reason:"not_guardian" }`, 500 `server_misconfigured` / `internal_error`
- **내부 로직**: ① `pet_guardians` 보호자 확인 ② `pets`에서 등록 종(`species_kind`)/품종(`species`)을 **서버가 직접 읽음**(클라 입력 불신) ③ **Gemini 2.5 Pro 영상 판별**(구조화 출력): 실제 살아있는 개/고양이 여부(dog/cat_real/fake ≥ 0.70 & real>fake), 영상 내내 동일 개체(`consistent`), 지시된 무작위 임무(challenge) 수행 여부(`challenges_done`), 추정 품종/털색 ④ 등록정보 교차검증 — 종/품종 불일치는 **소프트 경고**(통과에 영향 없음, `looseBreedMatch` 느슨 비교: 소문자·공백 제거 후 부분 포함, 믹스는 관대) ⑤ 통과 시 **프레임 N장만** `media/<uid>/pet_identity/<petId>/<i>.jpg`로 업로드(upsert) + `enroll_pet_identity` RPC(p_pet, p_species, p_breed, p_colors, p_info_match, p_paths, p_urls). **★ 영상은 저장하지 않음**(Gemini 인라인 전송 후 메모리에서 소멸 — Storage/DB 미기록).
- **내부 로직**: ① `pet_guardians` 보호자 확인 ② `pets`에서 등록 종(`species_kind`)/품종(`species`)을 **서버가 직접 읽음**(클라 입력 불신) ③ **Gemini 2.5 Pro 영상+프레임 한 호출 판별**(구조화 출력): 실제 살아있는 개/고양이 여부(dog/cat_real/fake ≥ 0.70 & real>fake), 영상 내내 동일 개체(`consistent`), **`frames[]`가 그 영상의 장면인지(`frames_from_video`) — 검증한 영상과 저장할 기준 프레임의 바꿔치기 차단**, 추정 품종/털색 ④ 등록정보 교차검증 — 종(개↔고양이) 불일치는 **하드 게이트**(`species_mismatch` 거절), 품종 불일치는 소프트 경고(`looseBreedMatch` 느슨 비교: 소문자·공백 제거 후 부분 포함, 믹스는 관대) ⑤ 통과 시 **프레임 N장만** `media/<uid>/pet_identity/<petId>/<i>.jpg`로 업로드(upsert) + `enroll_pet_identity` RPC(p_pet, p_species, p_breed, p_colors, p_info_match, p_paths, p_urls). **★ 영상은 저장하지 않음**(Gemini 인라인 전송 후 메모리에서 소멸 — Storage/DB 미기록). 실패 시에도 `photo_verifications(purpose='pet_identity')`에 1행 기록(관리자 실패 조회용).
- **시크릿**: `JWT_SECRET`, `GEMINI_API_KEY`, `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, (`ALLOW_ORIGIN`)
- **정책**: 라이브니스 하한 0.70(`ENROLL_REAL_THRESHOLD`), 프레임 최소 3장, 임무 최소 1개(전부 수행돼야 통과). Gemini 429 시 1.5s/3s backoff 최대 2회 재시도.
- **정책**: 라이브니스 하한 0.70(`ENROLL_REAL_THRESHOLD`), 프레임 최소 3장. 프레임↔영상 동일 출처(`frames_from_video`)는 2026-07-29부터 **섀도 모드**(challenge 를 오탐으로 제거한 전례에 따라 측정 후 전환 — 클라 프레임 추출 시각과 Gemini 1fps 샘플링 어긋남이 오탐 소지). **오탐률 측정**: 성공 등록마다 판정 1행을 `photo_verifications`에 기록 — 분모 = `purpose='pet_identity' AND result='pass'` 전체, 분자 = 그중 `fail_reason='frames_not_from_video_shadow'`. `_shadow` 접미사는 강제 전환 후의 실제 거절 행(`result='fail', fail_reason='frames_not_from_video'`)과의 영구 구분자. 판정 행은 즉시 만료(`expires_at=now()`)+`region_matched=false`+`image_url=null`이라 게시글 토큰으로 소비 불가. 인라인 페이로드 합계 한도 19M chars(`video_too_large` — 발생 시 실측값 console 로그 + 실패 행 기록, Gemini 요청당 20MB 보호선). Gemini 429 시 1.5s/3s backoff 최대 2회 재시도.

### search-petcafe

Expand Down
2 changes: 1 addition & 1 deletion docs/supabase-db.md
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ AI 반려동물 사진 검증 기록. 실사/생성 이미지 판별 점수, 개
- **FK**: user_id → users.id, pet_id → pets.id
- **CHECK**:
- `photo_verifications_result_check`: result IN ('pass','fail')
- `photo_verifications_purpose_check`: purpose IN ('reference','post')
- `photo_verifications_purpose_check`: purpose IN ('reference','post','pet_identity') — 'pet_identity'는 20260713112529에서 추가(varchar(20) 확장은 20260713112812)
- **인덱스**:
- `photo_verifications_user_idx` (user_id, created_at DESC)
- `photo_verifications_token_open_idx` (id, 부분: WHERE consumed_at IS NULL AND result='pass') — 미소비 통과 토큰 조회
Expand Down
Loading
Loading