Files
clean-architecture-frontend…/docs/architecture/presigned-transfer-and-image-cdn.md
T

41 KiB
Raw Blame History

Presigned transfer, resumable upload, streaming download and Image CDN

이 문서는 presigned URL, multipart/resumable upload, streaming download와 Image CDN을 브라우저 애플리케이션에 넣을 때의 control plane/data plane 경계, 무결성, 재개, 만료, 캐시와 복구 계약을 정의한다.

현재 구현된 개별 reference runtime은 실제 browser fetch와 bounded byte stream을 사용한다. 제품별 endpoint, bucket, CDN vendor, asset schema와 authorization owner는 아직 조합하지 않는다. 이 개별 runtime은 AVAILABLE_NOT_COMPOSED이고, Range/composition/provider 같은 후속 delta는 아래 표처럼 다른 상태다. 어느 경로도 임의의 URL이나 object key를 application caller가 직접 전달하는 범용 HTTP facade가 아니다.

0. 현재 구현과 목표 상태

이 문서에서 “설계됨”, “reference source가 있음”, “제품에 조합됨”과 “target browser에서 보장 가능함”은 서로 다른 사실이다. primary current status는 NOT_SELECTED, DESIGNED_NOT_IMPLEMENTED, AVAILABLE_NOT_COMPOSED, COMPOSED, PLATFORM_LIMITED 다섯 값 중 하나만 사용한다. production evidence와 traffic admission은 이 status와 별도 축이다.

capability primary current status 현재 있는 것 남은 목표
whole-object Presigned GET/part PUT AVAILABLE_NOT_COMPOSED strict BFF response validation, in-memory identity vault, bounded GET/PUT executor top-level wire version, 실제 endpoint/auth/revocation/provider contract
multipart/resumable upload AVAILABLE_NOT_COMPOSED server-authoritative session flow, part retry, IDB CAS checkpoint, Web Lock, cross-tab abort signal non-destructive pause, safe inventory/retention sweep, 실제 BFF/storage/scan
whole-object foreground streaming download AVAILABLE_NOT_COMPOSED 200 stream, length/SHA-256, picker save, bounded object URL와 browser handoff mechanism strategy selector, browser-managed capability issuer와 제품 save UX
Range resumable download DESIGNED_NOT_IMPLEMENTED VD-14 production design port/runtime/checkpoint, 206/200/412/416, seek/truncate 또는 OPFS staging
Image CDN policy/verification engine AVAILABLE_NOT_COMPOSED opaque asset/preset, responsive descriptor, P-256, static-image probe descriptor HTTP provider/refresh, renderer, 실제 BFF/CDN
app-managed background download NOT_SELECTED VD-14 경계와 금지 조건 제품이 별도 선택한 지원 browser에서만 optional 구현
cross-browser background-download guarantee PLATFORM_LIMITED browser-managed handoff fallback 공통 baseline으로 구현 완료를 선언하지 않음
app-managed background upload NOT_SELECTED pause/foreground resume와 명시적으로 분리 durable source staging/worker auth를 가진 별도 protocol
cross-browser background-upload guarantee PLATFORM_LIMITED foreground checkpoint resume fallback worker lifetime/source permission을 공통 보장하지 않음

현재 runtime은 테스트 전용 mock이 아니라 실제 browser API를 호출하지만, 위 남은 목표가 구현됐다는 뜻은 아니다. 특히 foreground whole-object streaming 증거를 Range resume나 app-managed background download 증거로 재사용하지 않는다. Range와 background download의 상세 상태 머신, destination, fallback, rollout과 완료 기준은 VD-14가 소유한다. top-level runtime composition, account teardown, Image descriptor provider/refresh와 safe presentation projection은 VD-16이 소유한다.

1. 경계와 topology

feature use case
  -> feature-specific transfer facade
       -> BFF/Web API control plane
            - authorization
            - upload session / download capability
            - resource metadata and lifecycle authority
       -> browser transfer data plane
            - capability validation
            - bounded fetch / stream / part transfer
            - integrity and cancellation
       -> object/file storage or CDN
            - bytes only

image use case
  -> ImageDeliveryPort
       -> backend/CDN-issued immutable asset descriptor
       -> policy-validated responsive candidates
       -> presentation-safe <picture>/<img> attributes

브라우저는 S3, MinIO, GCS, Azure Blob의 관리자 credential, bucket policy, object key 생성 규칙이나 signing key를 소유하지 않는다. control plane이 짧은 수명의 제한된 capability를 발급하고, data plane은 그 capability가 묶은 정확한 operation과 bytes만 전송한다.

2. 공통 capability 규칙

Presigned URL은 단순 URL이 아니라 bearer capability다. 구현은 적어도 다음 binding을 하나의 immutable snapshot으로 검증해야 한다.

  • protocol/version과 opaque capability ID
  • DOWNLOAD 또는 특정 upload session/part operation
  • logical resource/session ID
  • exact HTTP method
  • HTTPS와 composition allowlist에 속한 origin/path
  • capability가 발급한 exact query와 signed request headers
  • expected media type, byte range 또는 exact part offset/length
  • hard maximum bytes와 선택한 checksum algorithm/digest
  • upload 성공 response의 exact status, Content-Length, expectedResponseByteLength와 opaque receipt header
  • 발급·만료 시각과 composition의 더 짧은 lifetime ceiling
  • single-use가 필요한 경우 원자적인 server/provider consumption

2.1 Wire version 목표

현재 multipart DTO는 PRESIGNED_MULTIPART_V1을 exact하게 포함하지만, whole-object presigned capability response에는 top-level transfer protocol literal이 아직 없다. strict exact-key schema만으로 현재 payload drift는 막지만, 호환되지 않는 wire 변경과 구 client drain을 명시적으로 운영하기에는 부족하다.

향후 presigned control-plane envelope에는 다음 literal을 추가한다.

protocol = PRESIGNED_TRANSFER_V1
  • request/response, vault registration과 executor consumption에서 exact match한다.
  • 알 수 없는 값, 누락과 newer version은 fail-closed한다.
  • Range는 이 literal에 암묵적으로 섞지 않고 RANGE_RESUMABLE_DOWNLOAD_V1 별도 protocol을 사용한다.
  • Image descriptor HTTP envelope도 IMAGE_CDN_DESCRIPTOR_V1로 versioning한다.
  • version을 추가하기 전까지 현재 구현 상태는 계속 AVAILABLE_NOT_COMPOSED이며 wire-version 목표가 구현됐다고 기록하지 않는다.

URL의 query와 signed headers는 credential material로 취급한다. persistence, checkpoint, diagnostics, analytics, error message, referrer와 application state에 넣지 않는다. runtime caller가 URL/query/header를 조립하거나 capability의 method, origin, byte limit과 expiry를 늘릴 수 없다.

Data-plane fetch 기본값은 다음과 같다.

credentials = omit
redirect = error
referrerPolicy = no-referrer
cache = no-store
mode = same-origin or explicitly approved CORS

cross-origin object storage/CDN은 exact origin allowlist, CORS method/header, 노출할 response header와 CSP connect-src/img-src 계약이 있어야 한다. redirect를 따라가며 signed query를 다른 origin으로 전달하지 않는다.

3. Presigned URL

3.1 Control plane

실제 발급은 feature-specific BFF adapter가 소유한다. composition은 presigned capability 발급 endpoint를 HTTPS absolute URL 하나로 고정해 factory에 한 번 주입한다. application caller는 endpoint를 고르거나 path/query를 조립할 수 없다. 공통 runtime은 이 고정 endpoint와 strict envelope만 알고 provider-specific signing DTO는 알지 않는다.

권장 흐름:

browser -> authenticated BFF capability request
BFF -> resource/session authorization and metadata lookup
BFF -> object service signer
BFF -> opaque bound capability
browser -> validated direct data-plane request

발급 endpoint의 2xx만으로 authorization을 추론하지 않는다. runtime schema가 전체 capability를 검증하고, data request 시에도 object storage/BFF가 method, expiry, size/checksum 조건을 강제해야 한다. presigned URL은 underlying credential revocation이나 server policy 때문에 표기된 expiry보다 일찍 무효화될 수 있다.

upload part capability 요청에서 전달되는 requestBindingSha256, uploadBindingSha256와 part digest는 authorization proof가 아니다. UPLOAD_PART capability binding은 protocol: PRESIGNED_MULTIPART_V1을 exact하게 포함한다. BFF는 sessionId로 server-owned session을 다시 읽고 protocol, subject/purpose/state/expiry와 part plan을 authorization한 뒤 canonical binding을 직접 재계산해야 한다. client digest를 그대로 신뢰하거나 단순 echo해서 signed URL을 발급하면 안 된다.

3.2 Consumption

  • capability를 async 대기하는 동안 caller 입력과 dependency method를 snapshot한다.
  • 사용 전에 만료뿐 아니라 최소 잔여 lifetime도 검사한다.
  • 만료/403은 임의 retry가 아니라 control plane의 새 capability 발급으로 복구한다.
  • ambiguous network failure 뒤 upload part를 새 bytes로 덮어쓰지 않는다.
  • client-side single-use map은 UX 최적화일 뿐이다. cross-tab/replay authority는 server 또는 composition-owned atomic consumer다.
  • signed response의 URL, query, raw header와 exception text를 관측성에 남기지 않는다.

4. Multipart/resumable upload

이 reference runtime의 기본 모델은 PRESIGNED_MULTIPART_V1 capability 기반 ordered multipart protocol이다. 이 literal은 모든 upload control-plane request/response, session과 durable checkpoint에서 일치해야 하며 다른 값이나 누락은 fail-closed한다. 특정 S3 DTO를 application port로 노출하지 않으며 tus 같은 offset protocol을 선택하면 별도 protocol/version과 wire adapter가 동일한 상위 session contract를 구현한다.

4.1 Session contract

Control plane이 소유하는 operation:

  1. create: authorization, purpose, declared bytes/media와 source binding을 확인
  2. status/list parts: server-authoritative session/part state 반환
  3. issue part capability: exact session, part number, offset, length와 checksum binding
  4. complete: ordered part receipt와 checksum을 검증
  5. abort: server upload를 중단하고 orphan cleanup을 예약

browser HTTP adapter는 CREATE_SESSION, GET_STATUS, COMPLETE, ABORT의 closed operation set을 composition-owned fixed HTTPS endpoint map에 연결한다. 모두 bounded POST application/json이고 caller가 URL을 제공하지 못한다. upload control endpoint response는 exact URL/status/content type과 request/response byte cap, deadline, Retry-After ceiling을 검증한다. part capability는 앞 절의 별도 fixed BFF endpoint를 redirect 금지로 사용하고 strict response envelope 및 request/response의 UPLOAD_PART binding protocol이 PRESIGNED_MULTIPART_V1인지 대조한다.

Session은 적어도 opaque ID, protocol version, exact total bytes, media type, part size, part count, concurrency ceiling, checksum algorithm과 expiry를 묶는다. part number는 1부터 연속적이어야 하고 마지막 part를 제외한 part length는 고정한다.

PRESIGNED_MULTIPART_V1의 canonical digest 계약은 다음과 같다. 각 field는 아래 순서의 UTF-8 line으로 직렬화하고 SHA-256 lowercase hex를 사용한다.

fingerprint.digestHex =
  SHA-256(
    "SHA-256-PARTS-V1"
    fingerprint.byteLength
    fingerprint.partSizeBytes
    fingerprint.partCount
    "{partNumber}:{offset}:{byteLength}:{checksumSha256}" for each ordered part
  )

requestBindingSha256 =
  SHA-256(
    "RESUMABLE-UPLOAD-BINDING-V1"
    uploadKey
    purpose
    mediaType
    fingerprint.algorithm
    fingerprint.digestHex
    fingerprint.byteLength
    fingerprint.partSizeBytes
    fingerprint.partCount
  )

uploadBindingSha256 =
  SHA-256(
    "RESUMABLE-UPLOAD-SESSION-BINDING-V1"
    requestBindingSha256
    sessionId
    fingerprint.algorithm
    fingerprint.digestHex
    fingerprint.byteLength
    fingerprint.partSizeBytes
    fingerprint.partCount
  )

각 괄호 안의 항목은 실제로 줄바꿈 하나로 연결하며 마지막 빈 line은 추가하지 않는다. BFF는 create에서 받은 선언을 server policy/session snapshot과 함께 보관하고, part capability 발급 때 그 snapshot으로 request/upload digest와 exact part offset/length/checksum/idempotency를 재계산한다. complete에서는 server ledger의 ordered part set으로 fingerprint digest도 다시 계산한다. digest 일치는 input binding의 무결성 신호일 뿐 subject authorization, session ownership 또는 session state 검사를 대체하지 않는다.

4.2 Browser transfer

  • source는 bounded readRange(offset, length)를 제공한다.
  • part bytes 하나와 digest 계산에 필요한 copy만 메모리에 둔다.
  • partSize × concurrency × copyFactor가 composition memory ceiling을 넘으면 시작 전에 거절한다.
  • 각 part는 exact offset/length와 SHA-256을 계산한 뒤 capability를 발급받는다.
  • retry는 동일 session/part/offset/length/checksum/idempotency binding에만 허용한다.
  • 429/모든 5xx/network retry는 composition의 bounded attempt, bounded Retry-After와 abortable backoff 안에서만 수행한다.
  • 만료/authorization failure는 최대 정책 범위 안에서 capability를 재발급한다.
  • upload response의 opaque receipt/ETag를 whole-file digest로 해석하지 않는다.
  • PUT capability는 성공 status, receipt header와 expectedResponseByteLength를 묶는다. runtime은 exact Content-Length를 확인하고 response body를 hard cap과 동일 deadline 안에서 끝까지 bounded drain한 뒤에만 receipt를 성공으로 채택한다. 204는 expected response bytes가 0일 때만 허용하고 Content-Length 부재를 0으로 정규화한다.
  • complete 전 server-authoritative status와 local receipt를 reconcile한다.
  • complete 성공은 scan 완료가 아니라 QUARANTINED다.

4.3 Resume와 checkpoint

Checkpoint에는 다음만 저장할 수 있다.

  • schema version, exact PRESIGNED_MULTIPART_V1, revision과 lifecycle state
  • opaque upload key, session ID와 requestBindingSha256
  • SHA-256-PARTS-V1 fingerprint의 total bytes, part size/count와 digest
  • session expiry와 server concurrency ceiling
  • 완료 part의 number/offset/length/checksum/opaque receipt
  • 마지막 reconciliation 시각

Presigned URL, query, signed header, bearer token, file name, local path, account ID와 raw backend error는 저장하지 않는다. 위에 명시한 SHA-256 fingerprint/part checksum과 bounded opaque part receipt는 서버 reconcile에 필요한 비권한성 checkpoint field이므로 예외적으로 해당 account partition에만 보존한다. raw provider ETag를 임의로 저장하는 것이 아니며 이 필드들도 diagnostics, telemetry, ticket 또는 application-facing 결과에는 노출하지 않는다.

resume 시 checkpoint만 신뢰하지 않는다.

  1. 사용자가 다시 선택한 source의 exact byte length와 source binding을 검사한다.
  2. server status/list-parts를 authoritative하게 읽는다.
  3. 완료되었다고 주장하는 각 part의 local bytes를 다시 bounded hash하여 server checksum/receipt와 대조한다.
  4. 불일치하면 해당 session을 complete하지 않고 abort/restart 또는 사용자 복구로 전환한다.
  5. 새 part만 업로드한 뒤 전체 ordered set을 다시 reconcile한다.

GET_STATUS가 HTTP 404 또는 410을 반환하거나 decoded status가 NOT_FOUND/EXPIRED이면 해당 session은 terminal이다. runtime은 CAS revision을 확인해 checkpoint를 제거하고 새 session으로 restart하거나 EXPIRED_RESOURCE/RESTART를 반환한다. 사라진 session의 checkpoint를 다음 invocation까지 반복해서 붙잡지 않는다. abort의 404/410도 checkpoint를 정리하고 application에는 ORPHANED로 닫는다.

한 session은 cross-tab mutation lock으로 직렬화한다. lock은 correctness의 유일한 근거가 아니며 server idempotency와 part CAS가 최종 authority다.

명시적 abort는 같은 runtime의 controller뿐 아니라 strict RESUMABLE_UPLOAD_CANCEL_V1 BroadcastChannel을 통해 같은 origin의 다른 runtime에도 opaque upload key의 ephemeral cancel 신호를 보낸다. 수신 runtime은 진행 중 fetch/read/backoff의 AbortSignal을 먼저 중단해 Web Lock을 내보내고, abort 요청 runtime이 lock 안에서 durable checkpoint를 ABORT_PENDING으로 바꾼 뒤 server abort/reconcile을 실행한다. 이 메시지는 session ID, capability, signed URL이나 receipt를 포함하거나 저장하지 않으며 server state authority가 아니다. BroadcastChannel이 없으면 correctness는 유지되지만 abort는 caller가 정한 bounded deadline 아래 다른 context의 lock 해제를 기다린다.

사용자 cancel은 현재 browser work 중지이고 server abort와 다르다. 명시적 abort를 요청했는데 결과가 불명확하면 checkpoint를 즉시 성공으로 삭제하지 않고 다음 reconcile에서 server 상태를 확인한다. backend는 만료된 orphan multipart를 정리하는 TTL job을 가져야 한다.

application-facing upload 성공값은 state, opaque resourceId, byteLength, replayed만 반환한다. control-plane 검증에 사용한 sessionId, requestBindingSha256와 file fingerprint는 public outcome에 노출하지 않는다.

4.4 아직 구현되지 않은 pause와 checkpoint lifecycle 목표

현재 ResumableUploadPortupload()와 server-side abort()만 제공한다. caller가 자신의 AbortSignal을 중단하면 committed checkpoint가 남아 다음 upload()에서 resume할 수 있지만, 이것은 명시적 pause protocol이 아니다. 다른 tab의 active upload를 non-destructive하게 pause하는 API도 없으며 현재 cross-tab cancel signal은 explicit server abort를 준비하기 위한 신호다.

향후 pause를 선택하면 다음을 별도 version으로 구현한다.

pause(uploadKey)
  -> active read/fetch/backoff cancel
  -> RESUMABLE_UPLOAD_PAUSE_V1 ephemeral cross-context signal
  -> per-key mutation lock
  -> exact revision CAS to PAUSED
  -> in-memory part capability retirement
  -> no server multipart abort

resume through upload()
  -> PAUSED checkpoint validation
  -> server-authoritative status
  -> local completed-part re-hash
  -> CAS to ACTIVE
  -> missing parts only

현재 checkpoint schemaVersion: 1의 state는 ACTIVE | ABORT_PENDING뿐이다. PAUSED를 durable state로 추가한다면 unknown-old-writer behavior와 migration을 정한 schemaVersion: 2가 필요하다. 기존 schema에 필드를 몰래 추가하지 않는다.

현재 checkpoint store도 single-key read/CAS/remove와 account partition 전체 삭제만 제공한다. abandoned upload가 같은 uploadKey로 다시 열리지 않으면 local checkpoint를 retention 기준으로 자동 발견·정리하지 못한다. 향후 admin lifecycle은 다음을 제공한다.

  • account partition 안에서 cursor 기반 bounded safe-summary inventory
  • maximum age, count와 logical-byte budget
  • expired/terminal candidate의 server status 재확인
  • active lock/lease와 ABORT_PENDING을 무조건 삭제하지 않는 분류
  • checkpoint와 owned local staging이 있다면 같은 cleanup journal로 처리
  • cleanup response loss, blocked database와 CAS conflict reconciliation
  • logout/account deletion용 exact partition maintenance authority

inventory는 file name, path, account/resource/session ID, digest, receipt와 capability를 application이나 operator UI에 반환하지 않는다. 제품 resume UI가 display metadata를 필요로 하면 제품 repository가 opaque uploadKey와 별도로 소유한다.

이 pause/inventory/retention 항목은 현재 설계 목표이며 구현 완료가 아니다. app-managed background upload도 이 항목에 포함되지 않는다. worker upload는 source bytes의 durable staging, worker auth/reissue, version drain과 platform support가 승인된 별도 optional capability다.

5. Streaming download

Streaming download는 server resource를 전체 Blob/ArrayBuffer로 materialize하지 않고 Response.body를 closed-result byte source로 변환한다.

  • response는 200, non-opaque, non-redirect이고 body가 있어야 한다.
  • capability의 media type, content encoding 정책과 response header가 일치해야 한다.
  • native chunk는 configured output chunk ceiling으로 다시 분할한다.
  • 누적 bytes가 expected/hard maximum을 넘으면 즉시 reader를 cancel한다.
  • EOF에서 expected bytes보다 작으면 truncated failure다.
  • integrity-required policy는 vetted incremental verifier를 사용하고 destination close 전에 digest를 확인한다.
  • first failed chunk 뒤에는 더 이상 bytes를 노출하지 않는다.
  • abort/consumer early return에서 reader를 cancel하고 lock/capability lease를 해제한다.
  • byte source는 기본 one-shot이며 두 번째 stream 소비를 거절한다.

File System Access save picker가 있으면 user activation 안에서 destination을 먼저 열고 stream을 쓴다. close와 integrity verification이 끝난 경우만 SAVED다. anchor/navigation은 BROWSER_HANDOFF이며 disk write 완료를 의미하지 않는다. save picker가 없는 browser의 whole-Blob fallback은 별도 small-artifact hard cap 아래에서만 허용한다.

Range 기반 resumable download는 이 streaming contract와 다른 capability다. 도입하려면 validator-bound Range, 206 Content-Range, destination seek/truncate, ETag/If-Range와 final whole-object integrity를 별도 계약으로 추가한다. 단순히 partial bytes를 기존 파일 뒤에 append하지 않는다.

현재 presigned provider는 Range request header를 금지하고 GET success를 200으로 고정하며 executor는 Content-Range를 거절한다. save destination도 순차 writable만 제공한다. 따라서 이 문단은 구현 설명이 아니라 미구현 경계를 뜻한다.

목표 RANGE_RESUMABLE_DOWNLOAD_V1은 다음을 모두 포함한다.

  • immutable generation 또는 strong validator와 representation binding
  • 비권한성 account-partitioned checkpoint와 CAS offset
  • exact 206, Range 무시/validator mismatch의 200, mode가 계약한 412, 416 상태 머신
  • capability 재발급 뒤 representation binding 재검증
  • seek/truncate destination 또는 journaled OPFS staging
  • durable segment commit 뒤에만 checkpoint offset 전진
  • 완료 뒤 destination 전체 재읽기와 whole-object SHA-256
  • partial count/byte/age retention, crash/logout/account-switch cleanup
  • system picker 미지원 환경의 browser-managed handoff/server-generation fallback

상세 불변조건과 promotion gate는 VD-14를 따른다. Range의 primary current status는 DESIGNED_NOT_IMPLEMENTED다.

5.1 아직 구현되지 않은 strategy selector

현재 download strategy는 composition-registered file policy 하나에 BROWSER_MANAGED, PROMPT_AND_STREAM 또는 BOUNDED_OBJECT_URL로 정적으로 고정된다. source kind, exact size와 실제 picker/seek/OPFS 지원을 입력으로 안전한 fallback을 선택하는 공통 headless selector는 없다.

목표 selector는 user-agent 문자열이 아니라 capability probe를 사용한다.

조건 목표 결정
server file, 탭 종료 뒤 계속 필요 BROWSER_MANAGED_HANDOFF
verified foreground save, picker 지원 WHOLE_OBJECT_PICKER_STREAM
Range resume 필수, destination 계약 충족 RANGE_RESUMABLE_FOREGROUND
작은 generated artifact BOUNDED_OBJECT_URL
큰 generated artifact, picker 미지원 SERVER_GENERATION_REQUIRED 또는 UNSUPPORTED
background download가 선택되고 OPFS만 지원 APP_MANAGED_BACKGROUND_DOWNLOAD; app-private staging 후 foreground export

위 값은 application-level execution plan이다. 현재 file-delivery primitive에는 VD-16의 고정 mapping으로만 투영한다. source kind BROWSER_MANAGED_RESOURCE를 strategy로 사용하거나 PROMPT_AND_STREAM을 Range plan으로 재사용하지 않는다.

picker 미지원 때문에 unbounded Blob ceiling을 올리지 않는다. browser-managed handoff는 disk save나 integrity 완료를 관찰할 수 없으므로 계속 BROWSER_HANDOFF다. 이 selector와 picker 미지원 failure normalization도 현재 구현돼 있지 않다.

6. Image CDN

6.1 Asset와 preset

application은 raw source URL이나 arbitrary transform query 대신 opaque asset reference와 composition-registered preset reference를 전달한다. backend/CDN descriptor는 다음을 묶는다.

  • opaque asset ID와 immutable asset revision
  • public/private delivery class
  • source pixel dimensions와 안전 판정을 통과한 raster media type
  • named preset와 exact crop/fit intent
  • rendition별 format, natural width/height, URL와 expiry
  • private rendition이면 server-issued capability binding

CDN은 임의 external source URL을 transform parameter로 받지 않는다. backend asset registry가 quarantine/scan을 통과한 source object만 CDN asset ID로 promotion한다.

6.2 Policy validation

Composition policy가 소유하는 값:

  • application origin과 그 origin과 다른 allowed HTTPS CDN origins/path prefix
  • named preset와 allowed widths/DPR/formats
  • max natural/output width·height·pixels와 decoded/encoded bytes
  • crop/fit, quality와 static-raster 제한
  • maximum candidates와 minimum private URL lifetime
  • maximum concurrent capability verification
  • public/private cache, referrer와 credential policy

runtime은 caller가 preset의 width, DPR, quality나 format ceiling을 늘리지 못하게 한다. SVG/HTML/data/blob/javascript URL과 active/unknown media type은 기본 거절한다. 현재 protocol은 static raster만 지원하므로 animated format은 항상 거절하며, 도입하려면 별도 frame/decode budget protocol이 필요하다. Composition이 전달하는 hard limit은 IMAGE_CDN_IMPLEMENTATION_CEILINGS보다 항상 작거나 같아야 한다. 이 값은 제품 기본값이 아니라 adapter-owned 절대 상한이며 intrinsic/source/output pixel, decoded/encoded byte, candidate, URL, capability lifetime와 동시 cryptographic verification guard를 구성 실수로 해제하지 못하게 한다.

private capability의 signature는 versioned preset binding ID의 허용 집합을 묶는다. CDN/BFF는 그 ID를 server-owned immutable preset registry에서 조회하고 요청 query의 width/height/DPR/fit/format/quality가 registry가 산출한 exact candidate인지 다시 계산해 불일치 요청을 거절해야 한다. 브라우저가 만든 query, binding digest 또는 signature 문자열을 단순히 echo하거나 query 자체를 authorization proof로 취급하지 않는다.

capability policy의 acceptedKeyIds는 bounded unique overlap set이며 현재 signing key를 고르는 selector가 아니다. verifier의 immutable public-key registry는 runtime 생성 시 이 집합 전체를 포함해야 하고, descriptor의 단일 signature.keyId는 policy와 verifier 양쪽에 exact membership이 있어야 한다. rotation은 새 public key와 old/new overlap policy 배포, client 채택 확인, backend signer 전환, maxCapabilityLifetimeMs + maxClockSkewMs와 client rollout 기간 경과, old key 제거 순서를 따른다. 유출 key는 overlap 절차 대신 backend revocation과 runtime 재조합/강제 rollout 대상으로 다룬다.

browser probe는 encoded body를 hard cap 안에서 읽은 직후 native decoder 호출 전에 PNG/JPEG/WebP/AVIF header/container metadata를 파싱한다. 선언된 width/height, pixel 수와 decoded-byte ceiling을 먼저 확인하고 APNG/WebP animation, AVIF sequence/derived image와 ambiguous/malformed container를 fail-closed한다. 이 pre-decode 검사가 통과한 static raster만 createImageBitmap으로 실제 dimensions를 재검증한다.

6.3 Responsive descriptor

width descriptor를 쓰는 candidate는 모두 양의 고유 width를 가지며 오름차순으로 정렬한다. 같은 source set에서 wx descriptor를 섞지 않는다. sizes는 registry가 승인한 layout token에서 결정하고 arbitrary presentation 문자열을 CDN query에 넣지 않는다.

반환값은 presentation-safe descriptor다.

  • fallback src, intrinsic width/height
  • ordered format별 srcset
  • registry-owned sizes
  • loading, decoding, fetchPriority
  • referrerPolicy=no-referrer
  • application과 분리된 CDN origin의 asset은 crossOrigin=anonymous

private signed image는 expiry 전에 실제 load가 시작될 수 있는 eager/priority 정책만 사용하거나 load 직전에 새 descriptor를 발급한다. 오래된 signed URL을 DOM, persisted state, telemetry 또는 query cache에 장기 보관하지 않는다. private delivery는 PRIMARY_REQUIRED browser probe가 필수이며 이를 NONE으로 낮출 수 없다. probe는 credentials: omit, redirect 금지, exact response URL과 Cache-Control: no-store를 실제 response에서 확인한다. 실제 <img crossorigin="anonymous">는 same-origin일 때 cookie를 보낼 수 있으므로 registry는 CDN origin이 composition의 application origin과 같으면 생성 단계에서 거절한다. private CDN 응답은 cookie나 ambient authorization에 의존하지 않는다.

6.4 CDN cache와 invalidation

  • public rendition은 asset revision을 URL에 포함하고 public, max-age=..., immutable로 제공한다.
  • content가 바뀌면 purge에 의존해 같은 URL을 재사용하지 않고 revision을 바꾼다.
  • format은 URL에서 명시하거나 Vary: Accept 계약과 cache key를 정확히 맞춘다.
  • private rendition은 짧은 expiry와 필수 no-store를 쓴다.
  • CDN cache hit 여부는 authorization이나 asset safety proof가 아니다.

image probe의 단일 bounded deadline은 response header fetch, streamed body read와 native decode 전체를 포함한다. timeout/cancel/error 시 reader를 cancel하고, abort 뒤 늦게 resolve한 response body도 cancel하며 늦게 생성된 ImageBitmap도 즉시 close()한다.

Image CDN runtime의 close()는 terminal/idempotent다. application teardown, logout, account/tenant partition 변경 또는 runtime 교체 시 composition owner가 한 번 호출한다. runtime lifetime signal은 진행 중 capability verification과 probe를 중단하고, accepted capability WeakMap은 새 WeakMap으로 교체되어 기존 reference를 즉시 revoke하면서 strong reference를 남기지 않는다. 닫힌 runtime은 accept/resolve를 UNAVAILABLE로 거절하며 재개하지 않고 새 runtime을 조합한다.

6.5 아직 구현되지 않은 descriptor provider와 refresh

현재 Image CDN runtime은 trusted gateway가 이미 strict하게 decode했다고 가정한 BackendIssuedImageAsset 또는 composition-owned public descriptor를 runtime.assets에 전달받는다. opaque asset/preset validation, P-256 signature, responsive URL 생성과 browser probe는 구현돼 있지만 BFF에서 private descriptor를 가져오는 concrete HTTP provider는 없다.

향후 IMAGE_CDN_DESCRIPTOR_V1 provider는 다음 계약을 가진다.

  • composition-owned fixed HTTPS BFF endpoint
  • caller가 전달하는 값은 opaque product asset reference와 registered intention뿐
  • authenticated control-plane request, redirect 금지와 no-store
  • exact final response URL/status/content type/content length
  • fatal UTF-8 bounded JSON body와 unknown-field rejection
  • issuer, asset/revision, dimensions, delivery class, preset binding ID, issued/expiry와 P-256 signature의 exact schema
  • request deadline, caller/runtime abort와 late response-body cleanup
  • descriptor와 raw backend body를 query cache, persistence, log와 telemetry에 저장하지 않음

private descriptor가 minimum remaining lifetime 아래로 내려가면 presentation runtime이 기존 signed URL을 임의 연장하지 않는다. product-owned facade가 같은 opaque asset/intention에 대해 single-flight reissue를 수행하고, 새 descriptor를 다시 signature/registry/probe 경계에 통과시킨다. asset revision, preset binding, issuer 또는 account scope가 달라지면 기존 reference를 폐기하고 새 결과로 교체한다. logout, tenant switch, key compromise와 kill switch에서는 refresh를 중단하고 runtime을 close()한다.

P-256 key overlap과 runtime close는 현재 구현돼 있지만 dynamic key-set fetch, revocation epoch/list, descriptor auto-refresh와 backend scope revoke는 구현돼 있지 않다. 정상 key rotation은 composition의 immutable old/new registry 교체로, 긴급 회수는 backend revoke, runtime close와 forced rollout으로 처리한다.

presentation-safe descriptor를 실제 <picture>/<source>/<img>에 적용하는 renderer도 현재 공통 runtime 범위에는 없다. renderer primitive는 raw URL override를 받지 않고 descriptor 속성만 투영할 수 있지만, alt, placeholder, error/retry, SSR/preload와 analytics는 제품 presentation이 소유한다.

7. Failure와 recovery

조건 결과 복구
capability expired/revoked EXPIRED_RESOURCE 새 capability 발급
method/origin/path/binding mismatch POLICY_REJECTED 요청 재구성 금지
part status conflict CONFLICT server reconcile
part checksum mismatch INTEGRITY_FAILED 같은 bytes 재검증 후 retry/abort
response overrun/truncation INTEGRITY_FAILED destination abort, 새 download
session missing/gone EXPIRED_RESOURCE checkpoint 폐기 후 새 session
image preset/URL/pixel violation POLICY_REJECTED 안전한 placeholder/original policy
image capability verification concurrency ceiling LIMIT_EXCEEDED 진행 작업 종료 대기 또는 runtime 부하 조사
closed Image CDN runtime 사용 UNAVAILABLE 새 runtime composition
network/429/모든 5xx UNAVAILABLE bounded retry/backoff

관측성에는 operation, safe outcome, byte/part/candidate bucket, retry bucket과 failure code만 기록한다. capability ID, URL, query, asset/resource/session ID, file name, digest, raw ETag, receipt와 backend message는 log/diagnostics/telemetry에 기록하지 않는다. 앞 절의 strict checkpoint allowlist만 durable 예외다.

8. 조합 조건

현재 src/adapters/browser-transfer/index.ts는 개별 presigned, upload와 Image CDN factory를 export할 뿐 이들을 하나의 lifecycle과 account partition으로 묶는 top-level composition factory를 제공하지 않는다. browser file runtime, capability vault, upload runtime과 Image CDN runtime은 각자 dispose()/close()를 가지지만 logout, account switch와 partial cleanup을 하나의 admission fence 아래 실행하는 owner도 아직 없다.

제품 composition 전에 반드시 정할 것:

  • fixed BFF capability endpoint, closed upload endpoint map과 runtime schema
  • PRESIGNED_TRANSFER_V1IMAGE_CDN_DESCRIPTOR_V1 rollout/drain 계획
  • PRESIGNED_MULTIPART_V1 canonical binding 재계산과 server-side session lookup
  • same-origin proxy 또는 cross-origin CORS/object-storage topology
  • per-account/partition Web Lock namespace와 ephemeral BroadcastChannel cancel namespace, 미지원 환경의 bounded abort fallback
  • per-operation maximum bytes, part size/count/concurrency와 retry budget
  • upload success status/receipt header/expectedResponseByteLength와 response cap
  • checksum algorithm과 full/composite 의미
  • quarantine scan/promotion/status protocol
  • checkpoint classification, account scope, retention과 logout handling
  • application과 분리된 CDN origin, versioned preset exact 재계산, format/pixel/decoded-byte/cache/CSP contract, key rotation, verification concurrency와 runtime/probe deadline
  • download save/handoff UX와 partial destination recovery
  • browser matrix, fault injection, orphan cleanup과 CDN rollback runbook

이 값이 없으면 runtime factory를 bootstrap에 넣지 않는다. 선택하지 않은 runtime은 production module inventory와 removal gate로 기본 bundle에서 제외한다.

8.1 목표 top-level composition

향후 공통 factory의 책임은 dependency를 편리하게 묶는 것보다 구성 시 불변조건과 teardown 순서를 한 곳에서 강제하는 것이다.

BrowserTransferComposition
  product facade references
  strict runtime config snapshot
  browser file runtime
  browser-managed capability provider/vault
  presigned provider/vault/executor
  upload control transport/runtime/checkpoint admin
  download strategy selector
  optional Range runtime/checkpoint/destination registry
  image descriptor provider/runtime
  account lifecycle fence
  readiness/compatibility result
  safe observer
  kill switches
  close()

browser-managed handoff mechanism에는 synchronous resolver seam이 있지만, 실제 BFF에서 capability를 발급받아 user activation 전에 in-memory identity vault에 준비하는 concrete provider는 현재 없다. 목표 provider는 fixed endpoint, strict versioned response, safe receipt와 exact resource/media/extension/length/digest/expiry binding을 검증하고 raw href는 resolver 내부에만 둔다. handoff 시점에는 async 발급을 시작하지 않고 이미 준비된 exact identity만 synchronous consume한다.

composition의 close()는 terminal/idempotent하고 다음 순서를 보장한다.

  1. 신규 issue/upload/download/image resolve admission을 닫는다.
  2. active foreground work와 worker/channel을 abort한다.
  3. in-memory signed capability와 image reference를 revoke한다.
  4. writer, reader, Web Lock, BroadcastChannel과 runtime을 close한다.
  5. upload/Range checkpoint와 owned staging을 정책에 따라 reconcile한다.
  6. logout/account deletion이면 maintenance authority로 exact partition cleanup을 실행한다.
  7. blocked/ambiguous cleanup을 성공으로 표시하지 않고 safe recovery 결과로 남긴다.

primary status가 COMPOSED여도 completion ledger의 RuntimeHealth, PromotionEvidenceTrafficAdmission은 별도다. config schema, endpoint/auth, account partition, actual provider evidence, cleanup owner와 browser fallback 중 하나라도 없으면 TrafficAdmission=DISABLED, PromotionEvidence=MISSING | PARTIAL | EXPIRED로 fail-closed한다.

8.2 Contract harness 목표

현재 unit test와 Playwright route interception은 reference runtime의 failure와 browser API behavior를 검증하지만 reusable BFF/provider conformance suite는 아니다. server-file-capability-infrastructure.md의 provider contract matrix도 설계이며 실제 S3/MinIO/GCS/Azure/CDN adapter에 실행되는 source는 이 repository에 없다.

향후 harness는 같은 versioned fixture를 다음 네 등급에 실행한다.

등급 증명 범위
deterministic fake state machine, canonical binding과 failure mapping
intercepted browser route Fetch/CORS/stream/abort와 native destination
emulator/container provider SDK, multipart와 response header wiring
actual staging provider 실제 product/API/version/region의 constraint 강제

필수 frontend/BFF contract:

  • PRESIGNED_TRANSFER_V1 exact request/response와 unknown version rejection
  • expiry, wrong method/resource/range/part/header/query/origin rejection
  • URL/query/header/error/telemetry redaction
  • capability reissue와 server revocation
  • upload create/status/part/complete/abort, response-loss와 orphan reconcile
  • pause/checkpoint inventory가 구현될 경우 v1→v2 migration과 old-writer drain
  • browser-managed capability preload/consume와 BROWSER_HANDOFF truth
  • IMAGE_CDN_DESCRIPTOR_V1, signature/key overlap, expiry/reissue와 revocation
  • CDN preset exact recomputation, private no-store와 public immutable cache
  • Range를 구현할 경우 VD-14의 200/206/412/416 및 destination crash matrix

actual provider evidence에는 adapter/provider/config/contract version, artifact digest, environment/region, pass/fail/skip, fault result, waiver/owner와 expiry를 기록한다. required case skip, expired evidence와 contract/config 변경은 production promotion을 막는다. fake나 emulator success를 actual provider evidence로 승격하지 않는다.

9. 표준·vendor 참고