Files
tech-log-frontend/docs/operations/browser-transfer-recovery.md
T

321 lines
20 KiB
Markdown

# Browser transfer and Image CDN recovery
이 runbook은 presigned capability, multipart/resumable upload, streaming download와
Image CDN reference runtime을 제품에 조합한 경우에만 적용한다. skeleton의 기본
bootstrap은 이 runtime을 실행하지 않는다.
현재 Range resumable download와 top-level transfer/Image descriptor composition은
각각 `DESIGNED_NOT_IMPLEMENTED`다. app-managed background download와 upload는
각각 `NOT_SELECTED`, 그 cross-browser guarantee는 `PLATFORM_LIMITED`다. 아래에서
이 기능을 다루는 절차는 VD-14/VD-16 구현과 제품 조합이 완료된 뒤에만
활성화한다. 기존 whole-object streaming, foreground upload resume나
browser-managed handoff를 그 기능의 운영 증거로 사용하지 않는다.
## 1. 관측 신호
허용된 aggregate 신호:
- capability issue/claim의 success, expired, policy-rejected bucket
- upload session create/resume/reconcile/complete/abort outcome
- part size/count/concurrency와 retry bucket
- download expected/actual byte bucket과 truncated/overrun/integrity outcome
- Image CDN preset/format/width/candidate bucket과 policy rejection
- session age, orphan count, quarantine backlog와 promotion latency
금지:
- presigned URL, query, signed request/response header
- resource/session/asset/account ID와 object key
- file name, local path, raw ETag, checksum, receipt/token의 실제 값
strict upload checkpoint의 protocol-defined SHA-256 fingerprint/part checksum과
bounded opaque non-authorizing part receipt는 server reconcile을 위한 durable
예외다. account partition/retention 안에서만 보존하고 관측 신호나 ticket에는
절대 투영하지 않는다.
- raw backend/body/DOMException message와 stack
## 2. 초기 분류
| 증상 | 우선 확인 |
| --- | --- |
| capability expiry/403 급증 | BFF/object-store clock, signer credential rotation, expiry ceiling |
| CORS/preflight 실패 | exact origin, method, signed headers, exposed receipt/checksum headers |
| redirect/network failure | proxy/CDN redirect 변경, `redirect:error`, URL origin/path policy |
| 특정 part 반복 실패 | exact offset/length/checksum/idempotency, capability expiry |
| resume conflict | server status와 checkpoint revision/part binding |
| status 404/410 반복 | terminal checkpoint 제거와 새 session 생성 여부 |
| complete 뒤 file 불일치 | ordered parts, checksum type, ETag 오해, scan/promotion |
| download truncation/overrun | capability length, content encoding, proxy buffering/transform |
| destination partial save | picker writable abort/close, integrity-before-close |
| image policy rejection 급증 | asset revision, preset registry, dimensions, format/CDN origin |
| stale/broken private image | lazy load 시점과 signed URL minimum lifetime |
| image timeout/decode 실패 급증 | encoded cap, pre-decode metadata, decoded-byte ceiling, probe deadline |
| CDN cache poison 의심 | immutable revision, cache key, `Vary`, source asset registry |
## 3. Presigned capability
1. 신규 capability 발급을 중지할 kill switch가 동작하는지 확인한다.
2. BFF와 object storage의 clock skew와 signer credential lifetime을 확인한다.
3. whole-object control plane이 versioned target을 구현한 제품이면 request,
response, vault와 executor의 `PRESIGNED_TRANSFER_V1` exact match 및
unknown/missing version rejection을 확인한다. 현재 reference wire에 이
target이 아직 없으면 구현되지 않은 protocol을 통과했다고 기록하지 않는다.
4. capability가 method, object/part, content constraints와 expiry를 server-side로
강제하는지 확인한다.
5. upload part 발급 BFF가 `sessionId`로 server session을 조회하고
`PRESIGNED_MULTIPART_V1` canonical request/upload binding과 part plan을
재계산하는지, `UPLOAD_PART` capability binding에도 exact protocol을 넣는지
확인한다. client digest 일치만으로 authorization하지 않는다.
6. redirect가 추가되었으면 allowlist를 넓히지 말고 direct endpoint를 복구한다.
7. URL/query를 ticket 또는 log에 복사하지 않는다. 노출 가능성이 있으면 signer
credential/policy와 해당 capability scope를 폐기한다.
8. 만료 capability를 retry하지 않고 control plane에서 새 capability를 발급한다.
9. client claim은 server replay authority가 아니므로 중복 사용 여부는 server
access/audit의 안전한 aggregate로 판단한다.
## 4. Multipart/resumable upload
### 4.1 안전한 일시 중지
현재 runtime에는 공식 `pause()`와 checkpoint inventory/retention sweep이 없다.
caller abort 뒤 checkpoint가 남아 재개될 수 있다는 사실을 `PAUSED` 완료로
기록하지 않는다. 다음 절차의 `PAUSED` state와 탭 간 pause는 VD-16 목표
runtime이 구현된 제품에만 적용한다.
1. 신규 session create를 중지한다.
2. active part는 사용자가 취소하지 않았다면 현재 bounded attempt만 마무리한다.
3. complete를 중지하고 server status reconcile만 허용한다.
4. presigned URL은 checkpoint에 쓰지 않고 즉시 메모리에서 폐기한다.
5. 현재 runtime은 checkpoint를 `ACTIVE` 또는 `ABORT_PENDING`으로 유지한다.
목표 pause runtime은 exact scope/generation을 확인해
`PAUSE_REQUESTED -> PAUSED`로 전환한다.
6. 목표 pause runtime은 cross-context
`RESUMABLE_UPLOAD_PAUSE_V1`, checkpoint `schemaVersion: 2`
`ACTIVE | PAUSED | ABORT_PENDING` closed state를 사용한다. v1 old writer
drain, historical migration과 N-1 fail-closed를 확인한다.
7. 목표 runtime의 inventory/sweep는 count/cursor/deadline 안에서 abandoned와
expired 후보만 반환하고, server reconcile/CAS 없이 checkpoint를 삭제하지
않는다.
### 4.2 Resume
1. session/checkpoint/control DTO의 protocol이 모두
`PRESIGNED_MULTIPART_V1`인지 확인하고, session expiry와 source
binding/total bytes/part layout을 검사한다.
2. server-authoritative status/list-parts를 읽는다.
3. 완료 part의 local range SHA-256을 다시 계산해 exact checksum/receipt와 대조한다.
4. checkpoint-only part와 server-only part를 자동 complete하지 않는다.
5. 불일치는 session을 격리하고 새 session/명시적 abort를 선택한다.
6. missing part만 동일 bytes/checksum/idempotency binding으로 전송한다.
7. complete 직전에 1부터 연속적인 ordered receipt set을 다시 확인한다.
8. status HTTP 404/410 또는 `NOT_FOUND`/`EXPIRED`이면 stale checkpoint를 CAS
제거하고 새 session으로 restart한다. 같은 terminal session을 반복 조회하지
않는다.
9. network/429/모든 5xx retry가 attempt count, `Retry-After`, backoff와
operation timeout ceiling 안인지 확인한다.
### 4.3 PUT acknowledgement
1. capability가 expected success status, receipt response header와
`expectedResponseByteLength`를 exact하게 묶는지 확인한다.
2. 실제 `Content-Length`가 expected bytes와 같고 hard response cap 이하인지
확인한다. 204는 expected bytes가 0이어야 하며 header 부재를 0으로 정규화한다.
3. response body EOF까지 동일 deadline 안에서 bounded drain된 뒤 receipt가
accepted됐는지 확인한다.
4. receipt/ETag는 part acknowledgement일 뿐 whole-file digest나 authorization
proof로 해석하지 않는다.
### 4.4 Abort/orphan
- 사용자 cancel은 browser work cancel이며 server abort가 아니다.
- 다른 tab에서 명시적 abort하면 `RESUMABLE_UPLOAD_CANCEL_V1`의 opaque
upload-key-only BroadcastChannel 신호가 active read/fetch/backoff를 먼저
중단하고 Web Lock이 bounded 시간 안에 반환되는지 확인한다.
- BroadcastChannel 미지원/정책 차단 환경에서는 abort caller deadline 안에
lock을 얻지 못하면 성공으로 가장하지 않고 `ABORT_PENDING` 또는 safe
non-terminal failure로 닫은 뒤 server reconcile을 재시도한다.
- abort 결과가 불명확하면 checkpoint를 제거하지 않고 `ABORT_PENDING`으로 남긴다.
- 다음 invocation에서 server status가 `ABORTED/EXPIRED/NOT_FOUND`임을 확인한 뒤
checkpoint를 제거한다.
- abort endpoint의 HTTP 404/410은 terminal orphan으로 mapping하고 checkpoint를
제거한다.
- backend TTL cleanup은 비용과 보안 경계다. orphan upload count/bytes/age SLO를
운영한다.
- multipart complete는 `QUARANTINED`; scanner/CDR와 domain metadata commit이
끝나기 전 public delivery capability를 발급하지 않는다.
- application-facing success/log에는 session ID, request binding 또는 file
fingerprint를 노출하지 않는다.
## 5. Streaming download
1. capability media/length/digest와 실제 response header/body count를 비교한다.
2. `Content-Encoding`이 capability byte semantics와 다른지 확인한다.
3. overrun/truncation/integrity failure면 reader와 destination writable을 abort한다.
4. partial local destination을 성공으로 보고하지 않는다.
5. same resource를 새 capability와 새 destination으로 restart한다.
6. Range resume가 별도 승인되지 않았으면 기존 partial destination에 append하지
않는다.
7. anchor/navigation 결과는 browser handoff일 뿐 saved/verified 증거가 아니다.
### 5.1 Range resumable download
이 절차는 `RANGE_RESUMABLE_DOWNLOAD_V1` port/runtime, seekable destination 또는
owned OPFS staging과 provider contract가 구현·조합된 경우에만 적용한다.
1. checkpoint의 protocol, opaque account partition, representation binding,
total bytes, next offset, destination binding, revision과 retention을
검증한다. raw URL, signed header, validator, file path는 checkpoint나
incident ticket에 없어야 한다.
2. destination의 실제 durable length가 checkpoint offset과 다르면 append하지
않는다. 더 긴 uncommitted tail은 정책과 destination capability가 허용할 때만
`truncate()`하고, 더 짧으면 마지막 confirmed segment로 rollback하거나
새 staging으로 재시작한다.
3. 새 capability가 같은 immutable generation/strong validator, total length,
media type와 whole-object digest에 묶였는지 확인한다. capability의
`preconditionMode`, `allowWholeObjectFallback`과 exact `allowedStatuses`
확인하며 허용되지 않은 status에서는 body나 destination을 소비하지 않는다.
4. 허용된 `206`은 exact `Content-Range: bytes S-E/T`, `S=requestedStart`,
`E+1=requestedEndExclusive`, `Content-Length=E-S+1`, identity encoding과
capability precondition binding이 모두 맞을 때만 segment를 쓴다.
5. 허용된 full `200`은 response validator/generation evidence가 capability
representation binding과 같을 때만 평가한다. mismatch면 body를 쓰지 않고
staging/checkpoint를 quarantine한 뒤 새 representation으로 restart한다.
binding이 같아도 start가 0이고 whole-object fallback이 허용된 경우만 fresh
destination에서 consume하며, 그 밖에는 body를 쓰지 않고 Range reissue,
byte-0 restart, handoff 또는 unsupported로 닫는다. 일반 `If-Range` mismatch의
표준 응답은 full `200`이지 `412`가 아니다.
6. 허용된 `412`는 별도 `If-Match` 또는 provider generation precondition을 쓴
계약에서만 representation replacement로 처리한다.
7. 허용된 `416`은 checkpoint 완료 증거가 아니다. body를 data로 소비하지 않고,
final URL/header와 mode별 validator/generation binding을 먼저 검증한다.
provider response만으로 binding을 증명할 수 없으면 BFF reconcile이 exact
immutable generation을 증명해야 한다. 그 뒤 server total이 expected total과
같은지, `nextOffset <= total`인지, destination length와 offset이 일치하는지를
순서대로 reconcile한다. length mismatch는 먼저 truncate/verified rollback
또는 quarantine하고 같은 invocation에서 fall through하지 않는다.
`length == nextOffset == total`과 처음부터 다시 계산한 whole-object SHA-256이
모두 일치할 때만 final commit 후보가 된다. `length == nextOffset < total`이면
exact missing range를 재발급하고 반복 `416`은 contract mismatch로 닫는다.
8. capability expiry는 같은 representation binding으로 재발급한다. binding이
달라지면 기존 partial에 이어 쓰지 않는다.
9. segment flush와 destination length 재확인 뒤에만 checkpoint CAS를 전진한다.
10. final whole-object SHA-256과 destination close/commit 뒤에만
`SAVED_VERIFIED`로 기록한다.
malformed `Content-Range`, encoded representation drift, validator mismatch,
seek/truncate 실패 또는 crash 뒤 ambiguous tail에서는 신규 Range traffic을
중지하고 whole-object restart, browser-managed handoff 또는 명시적 unsupported로
degrade한다.
### 5.2 Background download와 browser-managed handoff
- `BROWSER_HANDOFF`는 브라우저 download manager가 이후 작업을 소유한다는
outcome이며 application progress, retry, destination integrity를 증명하지 않는다.
- page abort/reload 뒤 checkpoint가 남는 것은 app-managed background download 실행
증거가 아니다.
- app-managed background download를 선택한 제품만 별도 worker control plane, owned OPFS
staging, runtime/version/logout fence와 foreground export 절차를 운영한다.
- worker가 종료되거나 capability가 만료되면 checkpoint로 foreground recovery를
시도하며 장시간 keepalive를 가정하지 않는다.
- 미지원 Safari/Firefox/WebView에서는 browser-managed handoff 또는 명시적
unsupported UX를 사용한다. unbounded Blob fallback은 금지한다.
## 6. Image CDN
1. 신규 private descriptor 발급 또는 영향 preset을 kill switch로 중지한다.
2. 안전한 placeholder/original-approved rendition으로 fallback한다.
3. asset revision과 CDN cache key, format, width/height metadata를 확인한다.
PNG/JPEG/WebP/AVIF header를 native decode 전에 파싱해 선언 dimensions,
pixel/decoded-byte budget과 static-only 조건을 통과했는지 확인한다.
versioned preset binding ID를 server registry에서 조회해 요청의
width/height/DPR/fit/format/quality 전체를 재계산하고 query mismatch를
거절하는지도 확인한다.
4. 같은 immutable URL의 content가 변경됐다면 purge만으로 봉합하지 않고 새
asset revision을 발급한다.
5. signed private URL이 노출됐으면 expiry를 기다리지 말고 backend asset/capability
scope를 revoke한다.
6. private descriptor가 `PRIMARY_REQUIRED` probe를 사용하고 실제 response에서
exact URL, credential omission과 `Cache-Control: no-store`를 확인했는지
검사한다.
CDN origin이 application origin과 분리되어 실제
`<img crossorigin="anonymous">` 요청에도 application cookie가 실리지
않는지 배포 설정을 함께 확인한다.
7. fetch header/body/decode 전체 timeout과 abort cleanup을 확인한다. 늦게
resolve한 response body/`ImageBitmap`이 즉시 cancel/close되는지 fault
injection으로 재현한다.
8. source-fetch SSRF가 의심되면 arbitrary source URL transform을 차단하고
quarantine을 통과한 asset registry ID만 허용한다.
9. dimensions/pixel/decoded-byte budget 위반은 CDN transform과 descriptor
양쪽을 중지한다.
10. 정상 signing key 회전은 verifier와 `acceptedKeyIds`에 old/new key를 먼저
함께 배포하고 client 채택을 확인한 뒤 backend signer를 전환한다. 기존
capability lifetime, clock skew와 client rollout 기간이 모두 지난 후에만
old key를 제거한다.
11. key 유출, logout, tenant/account partition 변경 또는 feature teardown이면
backend capability를 revoke하고 기존 Image CDN runtime을 `close()`해 진행
중 verification/probe와 기존 reference를 폐기한 뒤 새 runtime을 조합한다.
12. descriptor HTTP provider를 설치했다면 response가
`IMAGE_CDN_DESCRIPTOR_V1`, fixed endpoint, strict body/content-type/deadline과
현재 runtime generation을 만족하는지 확인한다.
13. refresh는 asset/preset/scope/generation별 single-flight인지, 기존 descriptor
expiry 뒤 stale-while-error를 허용하지 않는지 확인한다.
14. logout/account switch 뒤 늦게 끝난 descriptor fetch/signature
verification/decode가 새 runtime에 채택되지 않는지 확인한다.
15. safe picture primitive가 verified presentation descriptor를 그대로
projection할 뿐 URL/query/transform을 재조립하지 않는지 확인한다.
## 7. Kill switch
- direct object-storage transfer off → same-origin BFF proxy 또는 기능 중지
- concurrent multipart → sequential part
- resumable off → 승인된 small-file simple upload 또는 upload 중지
- complete off → active session 유지/reconcile only
- upload pause/resume off → 신규 part 중지, explicit reconcile only
- streaming save off → authorized browser handoff
- Range resume off → whole-object restart 또는 authorized browser handoff
- app-managed background download off → browser-managed handoff 또는 foreground only
- app-managed background upload off → foreground checkpoint resume 또는 upload 중지
- Image descriptor refresh off → fresh descriptor expiry까지만 사용 후 placeholder
- private Image CDN off → authenticated same-origin placeholder
- advanced formats off → approved JPEG/PNG preset
- responsive candidates off → 한 개의 bounded fallback rendition
kill switch는 hard byte/pixel/security ceiling을 늘리지 않는다.
## 8. 복구 완료 조건
- capability expiry/replay/CORS 실패율이 baseline으로 복귀
- 신규·resume upload의 part checksum과 ordered complete 증적 통과
- orphan TTL cleanup과 quarantine backlog가 SLO 안으로 복귀
- download truncation/overrun/integrity fault injection 통과
- Range를 선택한 경우 `200/206/412/416`, capability reissue, destination
seek/truncate/crash와 final whole-object integrity matrix 통과
- pause/inventory를 구현한 경우 abandoned checkpoint retention/reconcile drill 통과
- image preset/revision/cache/CSP matrix 통과
- Image descriptor provider를 설치한 경우 expiry/refresh/logout generation
fence와 safe projection 통과
- image pre-decode static metadata, private no-store와 timeout cleanup fault 통과
- Chromium/Firefox/WebKit 동일 case set, zero failure/skipped
- raw URL/query/signed header/bearer token/capability가 log, telemetry,
checkpoint에 없음을 확인
- checkpoint allowlist의 SHA-256 fingerprint/part checksum/opaque receipt가
account partition과 retention 안에만 있고 log/telemetry에는 없음을 확인
## 9. 제거
1. 신규 capability/session/descriptor 발급 중지
2. active upload complete 또는 explicit abort와 orphan cleanup
3. non-secret checkpoint를 scope에 맞게 purge
4. private capability revoke와 CDN grace window drain
5. feature transfer/image facade와 composition 제거
6. browser-transfer source와 전용 tests/catalog wiring 제거
7. runtime removal gate, production module inventory, 전체 test/build 재검증
## 10. 관련 문서
- [Browser data capability completion ledger](../architecture/browser-data-capability-completion-ledger.md)
- [Presigned transfer and Image CDN](../architecture/presigned-transfer-and-image-cdn.md)
- [VD-14 Resumable download와 background download](../architecture/decisions/VD-14-resumable-download-and-background-transfer.md)
- [VD-16 Browser transfer composition과 Image delivery](../architecture/decisions/VD-16-browser-transfer-composition-and-image-delivery.md)
- [Server file capability infrastructure](../architecture/server-file-capability-infrastructure.md)