41 KiB
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:
create: authorization, purpose, declared bytes/media와 source binding을 확인status/list parts: server-authoritative session/part state 반환issue part capability: exact session, part number, offset, length와 checksum bindingcomplete: ordered part receipt와 checksum을 검증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, boundedRetry-After와 abortable backoff 안에서만 수행한다. - 만료/authorization failure는 최대 정책 범위 안에서 capability를 재발급한다.
- upload response의 opaque receipt/ETag를 whole-file digest로 해석하지 않는다.
- PUT capability는 성공 status, receipt header와
expectedResponseByteLength를 묶는다. runtime은 exactContent-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-V1fingerprint의 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만 신뢰하지 않는다.
- 사용자가 다시 선택한 source의 exact byte length와 source binding을 검사한다.
- server status/list-parts를 authoritative하게 읽는다.
- 완료되었다고 주장하는 각 part의 local bytes를 다시 bounded hash하여 server checksum/receipt와 대조한다.
- 불일치하면 해당 session을 complete하지 않고 abort/restart 또는 사용자 복구로 전환한다.
- 새 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 목표
현재 ResumableUploadPort는 upload()와 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에서 w와 x 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,fetchPriorityreferrerPolicy=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_V1및IMAGE_CDN_DESCRIPTOR_V1rollout/drain 계획PRESIGNED_MULTIPART_V1canonical 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하고 다음 순서를 보장한다.
- 신규 issue/upload/download/image resolve admission을 닫는다.
- active foreground work와 worker/channel을 abort한다.
- in-memory signed capability와 image reference를 revoke한다.
- writer, reader, Web Lock, BroadcastChannel과 runtime을 close한다.
- upload/Range checkpoint와 owned staging을 정책에 따라 reconcile한다.
- logout/account deletion이면 maintenance authority로 exact partition cleanup을 실행한다.
- blocked/ambiguous cleanup을 성공으로 표시하지 않고 safe recovery 결과로 남긴다.
primary status가 COMPOSED여도 completion ledger의 RuntimeHealth,
PromotionEvidence와 TrafficAdmission은 별도다. 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_V1exact 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_HANDOFFtruth 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 참고
- Browser data capability completion ledger
- Fetch Standard
- RFC 9110 HTTP Semantics
- File System Standard
- VD-14 resumable download와 background download
- VD-16 browser transfer composition과 image delivery
- HTML responsive images
- tus resumable upload protocol
- Amazon S3 presigned URLs
- Amazon S3 multipart upload