# Mobile Provider And External Link Policy

이 문서는 APP에서 반복되는 고객센터, 전화, 지도, 파일 viewer, upload provider, 외부 링크 정책의 source of truth다. 화면 PR마다 임의로 `Linking.openURL`, 새 native dependency, 임시 toast를 추가하지 않기 위해 이 adapter boundary를 우선한다.

이 문서의 provider-pending surface는 "frontend에서 상태를 설명할 수 있음"을 뜻한다. 실제 provider credential, bucket, OCR vendor, runtime URL이 연결됐다는 뜻이 아니다. Provider adapter shape는 `docs/20-contracts/PROVIDER_ADAPTER_CONTRACT.md`를 따르고, provider-ready 승격은 별도 구현 PR이 필요하다.

## Scope

| 영역 | 현재 결정 | 후속 결정 |
|---|---|---|
| 고객센터 | OS browser 또는 `expo-web-browser` adapter로 연다. 앱 내부 WebView는 기본값이 아니다. | 실제 URL, 로그인 handoff, query parameter 값 |
| 연락처/전화 | 기본은 provider-pending disabled CTA다. 실제 전화 연결은 adapter에서만 `tel:` deep link를 사용한다. | phone number source, masking, audit 기록 |
| 지도/경로 | 앱 내부 mock route guide를 우선 보여주고, 외부 지도는 adapter boundary에서만 연다. native map SDK는 기본값이 아니다. | 지도 provider, 좌표/주소 runtime 값 |
| PDF/반입 확인서 | provider-pending read-only viewer를 기본으로 둔다. local/dev backend는 기존 `DocumentRecord`에서 short-lived `DocumentAccessCapability`를 발급할 수 있다. 실제 production 파일 URL이 있으면 adapter에서 OS browser 또는 document viewer로 연다. | production signed read URL, download/share 권한 |
| 이미지 viewer | 앱 내부 image preview를 우선한다. 원격 파일은 signed URL capability를 통해서만 연다. | thumbnail/original capability와 cache policy |
| 동영상 viewer | Expo SDK 56 기준 `expo-video`를 앱 dependency로 둔다. 현재는 채팅방에 번들된 test video asset을 mock uploaded attachment로 재생 검증하는 범위다. | native rebuild evidence, playback/cache policy, 원격 video URL shape |
| 파일 선택/upload | mock attachment 또는 local capture artifact까지만 APP에서 다룬다. 실제 upload는 backend-issued S3-compatible presigned upload session shape로 연결한다. local provider-ready 개발은 floci S3로 반복하고, real smoke는 #910 dev bucket으로 검증한다. | production bucket/credential, retry queue, retention/deletion |

## Provider Boundary Rules

- 화면 컴포넌트는 provider SDK, credential, raw URL 조립을 직접 알지 않는다.
- 모든 외부 이동은 role/surface별 adapter 함수를 거친다.
- adapter 이름은 기능 의도를 드러낸다. 예: `openCustomerSupport`, `openPhoneDialer`, `openRouteMap`, `openReceiptDocument`, `createUploadDraft`.
- provider-ready upload/OCR/document adapter는 `PROVIDER_ADAPTER_CONTRACT.md`의 `FileUploadSession`, `StoredFile`, `OcrJob`, `DocumentAccessCapability` shape를 따른다.
- 새 native dependency가 필요하면 구현 PR이 아니라 decision PR을 먼저 만든다.
- provider가 없으면 CTA를 숨기지 말고 disabled reason 또는 provider-pending state로 보여준다.
- provider-pending UI는 실패가 아니라 “연결 예정” 상태다. 사용자에게 다음 행동이 없으면 disabled CTA로 둔다.
- `provider-pending-covered`는 stable `testID`와 fallback/disabled reason이 있다는 뜻이며, `provider-ready`나 실제 adapter 계약 완료를 뜻하지 않는다.
- UX index/register의 `missing:*` provider marker는 APP `testID`가 아니라 decision backlog marker다.
- 실제 provider failure는 `retry`가 가능한 경우에만 retry CTA를 보여준다.
- raw backend body, 인증값, 전화번호 원문, signed URL, 파일명은 Sentry나 PR 로그에 남기지 않는다.

## Decision Gate

아래 placeholder는 provider policy만으로 provider-ready 구현을 시작하지 않는다.

| Placeholder | 현재 허용 | 결정 전 금지 |
|---|---|---|
| `file-ocr-placeholder` | driver/processor upload/OCR provider-pending, error, offline, retry UI; upload/OCR adapter shape는 결정됨. local S3는 floci, local OCR은 deterministic fake, real OCR은 Google Cloud Vision으로 결정됨 | production bucket/credential, durable offline queue, retention/deletion |
| `external-links-placeholder` | customer support, phone, map, file viewer pending/fallback UI; handoff adapter shape는 결정됨 | runtime URL/phone/map values, audit policy, signed URL provider implementation |
| `realtime-chat-placeholder` | mock attachment draft, backend-mode `chat_attachment` upload-session draft, list badge/read-state sync, provider-pending contact surface, backend read-state persistence | message attachment persistence/linking, APP foreground realtime transport, OS push provider |
| `provider-decision-backlog` | provider-ready 전 결정 checklist와 halt 조건 | SDK/native dependency 추가, raw URL 조립, backend/provider shape 확정 |

## Standard Provider States

| State | 의미 | UI 기준 | TestID suffix |
|---|---|---|---|
| `mock-ready` | mock data로 화면 흐름 검증 가능 | 일반 card/preview처럼 렌더링하되 mock/provider-pending copy를 함께 둔다 | `.provider.mock-ready` |
| `provider-pending` | 실제 provider/API 미연결 | disabled CTA, pending badge, 연결 예정 copy | `.provider-pending.message` |
| `provider-disabled` | 현재 role/status에서 사용할 수 없음 | disabled reason을 명시한다 | `.provider-disabled.reason` |
| `provider-error` | provider 호출 실패 | 재시도 가능 여부를 copy와 CTA로 분리한다 | `.provider-error.message` |
| `provider-offline` | 네트워크 불안정/현장 오프라인으로 provider 호출 보류 | 로컬 대기/동기화 예정 copy, retry 또는 offline CTA | `.provider.offline` |
| `provider-ready` | 실제 provider contract 연결 완료 | adapter 성공/실패 branch를 Maestro 또는 unit test로 고정한다 | `.provider-ready.state` |

## Copy Rules

- provider가 아직 없을 때는 “지원하지 않음”이 아니라 “연결 준비 중” 또는 “제공자 연동 대기”로 표현한다.
- disabled reason은 사용자의 잘못처럼 보이지 않게 쓴다.
- 외부 앱으로 나갈 때는 “외부 앱으로 이동” 또는 “브라우저에서 열기”처럼 이동 맥락을 명시한다.
- retry copy는 같은 작업을 다시 시도한다는 점을 분명히 한다.
- 기술명(S3, presigned URL, OCR engine, provider adapter)은 사용자 화면에 노출하지 않는다.

## Role Surface Policy

### Emitter

- 고객센터 CTA는 provider-ready 전까지 `external-support.web-handoff.screen`으로 수렴하고, URL preview와 browser handoff pending CTA를 보여준다. 실제 URL/auth handoff가 확정되면 `openCustomerSupport` adapter로만 연다.
- 배출 상세의 반입 확인서는 provider-pending viewer를 기본으로 한다.
- backend detail이 `receiptDocumentId`를 내려주면 APP adapter는 `DocumentAccessCapability`를 요청할 수 있다. 실제 PDF 저장, 공유, 다운로드는 file provider와 권한 정책 확정 전까지 disabled로 둔다.

### Dispatcher

- 배차 detail의 반입 확인서는 read-only provider-pending preview를 유지한다.
- 지도/경로 CTA는 앱 내부 route guide를 먼저 열고, 외부 지도 이동은 adapter가 provider-ready일 때만 허용한다.
- 배차 취소/재배차는 backend workflow contract가 필요하므로 provider policy 문서만으로 구현하지 않는다.

### Driver

- 전화/연락처 CTA는 phone provider-ready 전까지 provider-pending disabled CTA다. schedule contact sheet는 `driver.schedule.contact.dialer-pending.*` selector를 사용한다.
- 경로 안내는 mock route guide를 우선하고, 외부 지도 deep link는 provider adapter 뒤에 둔다. provider-ready 전 `driver.route-guide.external-map.cta`는 `driver.route-guide.external-map.fallback`을 보여준다.
- continuous realtime GPS 관제와 background location은 MVP 범위가 아니다. 운전자 위치는 `docs/20-contracts/LOCATION_TRACKING_CONTRACT.md` 기준으로 explicit driver action의 foreground event-time evidence까지만 허용한다.
- 상차/계근/하차 사진과 OCR은 local artifact/mock result까지 화면에서 표현하고, upload/OCR provider는 별도 adapter로 승격한다.
- provider-ready 승격 전 local backend proof는 floci S3 + fake OCR을 기준으로 하고, Google Cloud Vision 품질은 `btalk-dev` real smoke에서만 판단한다.
- offline/retry UI는 mock-first 상태를 유지하되 실제 queue/persistence는 후속 provider/API issue에서 다룬다.

### Processor

- 계근 사진/OCR은 provider-pending/error state를 화면에서 분리한다.
- provider-ready 전까지 계근 사진/OCR은 `provider-pending`, `provider-error`, `provider-offline`을 APP-local state로만 표현한다.
- 처리 실적 등록 이후 file upload 또는 올바로 연동은 backend/provider contract 확정 전까지 lifecycle card projection만 유지한다.
- 반입 확인서/증빙 파일 viewer는 signed URL contract가 없으면 read-only mock/provider-pending surface로 둔다.

### Common Chat

- 전화 연결은 phone provider-ready 전까지 disabled contact CTA로 둔다.
- attachment picker/upload는 Gifted Chat action sheet 기반으로 사진, 동영상, 문서 선택지를 노출한다. mock mode는 local draft와 local send result를 유지하고, backend mode는 backend-issued upload session으로 `chat_attachment` draft를 provider-ready 상태까지 올릴 수 있다.
- 사진 preview는 기존 `expo-image`를 우선 사용한다. 동영상 preview는 Expo SDK 56 호환 `expo-video`를 사용하되, 현재 provider-ready 범위는 아니다. `coupang-incheon-1` 채팅방의 번들 test image/video uploaded attachment는 `expo-asset`으로 해석해 렌더링을 검증한다.
- `expo-video`처럼 native dependency가 추가되면 `expo install --check`, `expo-doctor`, prebuild/build evidence를 남긴다.
- 실제 file picker와 message attachment persistence/linking은 chat/file provider issue에서 다룬다. Adapter shape는 `docs/20-contracts/PROVIDER_ADAPTER_CONTRACT.md`의 upload session contract를 따른다. APP foreground realtime transport와 OS push provider는 `docs/20-contracts/REALTIME_PUSH_CONTRACT.md`와 별도 native dependency decision을 따른다. Backend read-state persistence는 이미 `/chat/{roomId}/read`로 승격됐다.

### Local Provider Harness

- provider readiness doctor는 `pnpm provider:doctor`로 실행한다. local smoke를 함께 검증하려면 `pnpm provider:doctor -- --run-local-s3`를 사용한다.
- doctor의 `ok`는 provider report가 정상 생성됐다는 뜻이고, `strictOk`는 실제 provider promotion gate가 모두 통과하거나 실행 가능한 상태라는 뜻이다. AWS dev smoke bucket은 #910에서 생성됐으므로 real S3 promotion gate는 `pnpm provider:doctor -- --run-real-s3 --s3-bucket=<dev-bucket> --strict`로 재검증한다. `--strict`는 미충족 provider gate를 실패로 처리한다.
- doctor output은 signed URL, credential, raw OCR text, local file path를 출력하지 않는다.
- floci S3 smoke는 `pnpm provider:s3:smoke:floci`로 실행한다.
- 이 smoke는 local emulator에 bucket을 만들고 presigned PUT/GET, `head-object`, cleanup을 검증한다.
- smoke output은 signed URL, credential, raw object key 전체를 출력하지 않는다.
- real AWS S3 smoke는 `pnpm provider:s3:smoke:aws -- --bucket=<dev-bucket>` 또는 `pnpm provider:doctor -- --run-real-s3 --s3-bucket=<dev-bucket> --strict`로 실행한다. #910 dev bucket과 AWS credential이 있는 환경에서만 돌리며, tiny object upload/read/delete cleanup만 검증한다.
- `btalk-dev` Google Vision smoke는 `pnpm provider:doctor -- --run-gcv --gcv-image=<sample>` 또는 `pnpm provider:ocr:smoke:gcv -- --image=<sample>`로 실행한다. #908에서 Cloud Vision API enable 후 `DOCUMENT_TEXT_DETECTION` + `ko` language hint 기준 smoke가 통과했다. raw OCR text는 출력하지 않는다.
- real AWS S3 smoke와 `btalk-dev` Google Vision smoke는 별도 provider-ready gate이며, 기본 local APP/Maestro loop에서 요구하지 않는다. 현재 남은 provider gate는 production bucket/secret, retention/deletion, durable queue, signed document read URL 같은 운영 구현이다.

## Testability

- provider 상태는 visible copy 대신 stable `testID`로 고정한다.
- 권장 패턴:
  - `{role}.{surface}.{provider}.provider-pending.message`
  - `{role}.{surface}.{provider}.provider-disabled.reason`
  - `{role}.{surface}.{provider}.retry.cta`
  - `{role}.{surface}.{provider}.open-external.cta`
- 외부 앱을 실제로 열어야 하는 테스트는 Maestro happy path의 기본값으로 두지 않는다. 대신 mock route guide, provider-ready adapter unit test, 또는 provider-pending UI를 검증한다.
- provider-ready 전환 PR은 adapter success/failure branch unit test와 최소 1개 Maestro 또는 agent-device evidence를 포함한다.

## Implementation Checklist

새 provider/external-link 기능을 구현할 때는 PR 본문에 아래를 적는다.

- 관련 Figma node 또는 issue
- 사용 adapter 이름
- provider state: `mock-ready`, `provider-pending`, `provider-disabled`, `provider-error`, `provider-ready`
- stable `testID`
- provider가 없을 때 사용자에게 보이는 copy
- 실제 외부 앱/파일/전화/지도 이동 여부
- Sentry 전송 여부와 redaction 기준
- Maestro 또는 agent-device evidence

## Open Decisions

- 실제 고객센터 URL과 인증 handoff 값
- phone dialer provider-ready 시점과 audit 기록 구현
- 지도 provider와 좌표/주소 runtime 값
- future continuous `gps_track`, background location, O-12 validation, retention
- production PDF/document signed read URL provider 구현과 저장/share 권한
- production S3-compatible bucket/credential provisioning
- OCR vendor/API benchmark와 threshold
- durable upload/OCR offline queue와 persistence
- APP foreground realtime transport
- OS push provider
