Files
clean-architecture-frontend…/docs/architecture/decisions/VD-14-resumable-download-and-background-transfer.md
T

42 KiB

VD-14: Resumable download와 background download 경계

  • 상태: Accepted — production design complete, implementation pending
  • 결정일: 2026-07-28
  • catalog recipe availability: RECIPE_AVAILABLE (primary status/selection과 별도)
  • Range resumable download primary status: DESIGNED_NOT_IMPLEMENTED
  • app-managed background download primary status: NOT_SELECTED
  • cross-browser app-managed background download guarantee: PLATFORM_LIMITED
  • 관련 결정: VD-10, VD-11, VD-12
  • current status ledger: docs/architecture/browser-data-capability-completion-ledger.md
  • 재검토: 제품이 Range 재개, 탭 종료 뒤 전달 또는 대용량 Safari fallback을 선택할 때

1. 배경과 현재 사실

현재 reference runtime은 whole-object 200 response를 bounded stream으로 읽어 foreground destination에 저장하거나 browser download manager에 handoff한다. 이 경로는 전체 payload를 하나의 Blob으로 만들지 않고 byte length와 SHA-256을 검증하지만, 네트워크나 탭이 중단되면 다음 실행은 byte 0부터 다시 시작한다.

Range resume는 기존 stream에 Range header 하나를 추가하는 기능이 아니다. representation identity, exact 206 Content-Range, durable partial destination, checkpoint CAS, 200/412/416 reconciliation과 마지막 whole-object integrity가 하나의 protocol이어야 한다. background download도 Range resume와 동일하지 않다. 브라우저 download manager에 넘기는 것과 애플리케이션이 Service Worker에서 전송을 계속 관리하는 것은 완료 증거와 상호운용성이 전혀 다르다.

이 ADR은 목표 계약을 정의한다. 이 문서가 존재한다는 사실은 runtime, endpoint, worker 또는 제품 UX가 구현·조합되었다는 뜻이 아니다.

2. 표준 capability 상태

설계, source 존재, 제품 조합과 플랫폼 한계를 하나의 enabled boolean으로 합치지 않는다. primary current status는 다음 다섯 값 중 정확히 하나다. 이 taxonomy는 선형 maturity model이 아니며 상태 이름만으로 rollout 또는 production readiness를 추론하지 않는다.

primary status 의미
NOT_SELECTED 제품 요구, owner, policy 또는 구현 범위가 아직 선택되지 않음
DESIGNED_NOT_IMPLEMENTED versioned contract와 불변조건은 승인됐지만 reference source가 없음
AVAILABLE_NOT_COMPOSED 검증 가능한 reference source가 있지만 제품 bootstrap/endpoint에는 연결되지 않음
COMPOSED 특정 제품 facade, config와 dependency에 실제로 조합됨
PLATFORM_LIMITED 요구 semantics를 target browser/platform 전체에서 보장할 수 없음

production readiness와 traffic admission은 primary status와 독립된 축이다. 운영 상태는 completion ledger가 정의한 네 canonical 축만 사용한다.

Selection =
  NOT_SELECTED | SELECTED | REMOVING
TrafficAdmission =
  DISABLED | SHADOW | CANARY | ENABLED
RuntimeHealth =
  UNKNOWN | AVAILABLE | DEGRADED | UNAVAILABLE | INCOMPATIBLE
PromotionEvidence =
  MISSING | PARTIAL | COMPLETE | EXPIRED

아래 source evidence는 ADR과 reference source의 존재를 설명하는 문서 표기일 뿐 canonical readiness 축이 아니다. DESIGN_REVIEWED는 native browser나 provider 증거가 아니고, REFERENCE_TESTED도 ledger의 browser component를 PROMOTABLE 또는 PromotionEvidence=COMPLETE로 만들지 않는다.

현재 capability별 판정:

capability primary status source evidence 비고
whole-object foreground streaming AVAILABLE_NOT_COMPOSED REFERENCE_TESTED 기존 VD-12 범위
browser-managed handoff mechanism AVAILABLE_NOT_COMPOSED REFERENCE_TESTED 실제 capability issuer는 제품 연결 시 필요
Range resumable download DESIGNED_NOT_IMPLEMENTED DESIGN_REVIEWED 이 ADR의 구현 대상
app-managed background download NOT_SELECTED DESIGN_REVIEWED 제품 요구가 선택될 때만 별도 구현
cross-browser app-managed background download guarantee PLATFORM_LIMITED DESIGN_REVIEWED 공통 baseline으로 promotion 불가

기존 foreground stream과 browser-managed handoff의 source/evidence를 Range나 app-managed background download 구현 증거로 재사용하지 않는다.

3. 결정 요약

  1. whole-object foreground streaming, Range resumable download, browser-managed handoff와 app-managed background download를 서로 다른 capability와 결과 타입으로 유지한다.
  2. Range protocol literal은 RANGE_RESUMABLE_DOWNLOAD_V1로 고정한다. 기존 whole-object presigned contract에 암묵적으로 섞지 않는다.
  3. resume의 authority는 server-owned immutable generation과 strong validator다. local offset, file name, timestamp 또는 partial byte 존재는 authority가 아니다.
  4. 각 data-plane capability는 exact representation, start/end range, method, response status/header/length와 expiry를 묶고 한 번만 사용한다.
  5. checkpoint는 비권한성 recovery metadata만 account-partitioned storage에 보관한다. URL, signed query/header, raw ETag, bearer token과 file path는 저장하지 않는다.
  6. destination은 seek/truncate 가능한 명시적 port 또는 owned OPFS staging이다. 순차 writable에 검증되지 않은 partial bytes를 append하지 않는다.
  7. checkpoint offset은 destination segment가 durable하게 commit되고 exact length가 재확인된 뒤에만 CAS로 전진한다.
  8. final success는 destination 전체를 처음부터 다시 읽어 whole-object SHA-256을 검증하고 final commit을 마친 경우만 SAVED_VERIFIED다.
  9. browser-managed handoff는 탭 종료 뒤 계속될 수 있는 기본 server-file fallback이지만 결과는 계속 BROWSER_HANDOFF다.
  10. app-managed background download는 cross-browser baseline이 아니다. 별도 optional protocol, platform probe, worker control plane과 owned staging이 모두 승인된 환경에서만 progressive enhancement로 조합한다.
  11. browser 차이는 user-agent 문자열이 아니라 capability probe와 정책으로 결정한다.

4. Topology와 책임

product download use case
  -> product-owned download facade
       -> DownloadStrategySelector
            -> WHOLE_OBJECT_PICKER_STREAM
            -> RANGE_RESUMABLE_FOREGROUND
            -> BROWSER_MANAGED_HANDOFF
            -> BOUNDED_OBJECT_URL
            -> APP_MANAGED_BACKGROUND_DOWNLOAD (optional)

RANGE_RESUMABLE_FOREGROUND
  -> BFF control plane
       authorization
       immutable representation lookup
       range capability issuance/reissue
  -> browser RangeDownloadRuntime
       checkpoint + mutation lock
       exact HTTP state machine
       seekable destination or OPFS staging
       whole-object verification
  -> object store/BFF byte plane

APP_MANAGED_BACKGROUND_DOWNLOAD
  -> window-owned admission and user intent
  -> worker-specific control plane
  -> owned OPFS staging
  -> later foreground export

브라우저는 bucket, object key, provider generation locator, signing key 또는 cloud credential을 소유하지 않는다. BFF가 logical resource를 exact immutable representation에 binding한다. direct object-store Range가 해당 binding과 capability의 preconditionMode가 선택한 exact If-Range 또는 immutable-generation precondition을 실제로 강제하지 못하면 BFF proxy/relay를 사용한다.

5. Versioned Range capability

5.1 Application-visible handle

application에는 raw URL이나 validator를 노출하지 않는다.

RangeDownloadCapability
  protocol = RANGE_RESUMABLE_DOWNLOAD_V1
  opaque identity
  safe receipt
  resourceId
  representationBindingSha256
  totalByteLength
  mediaType
  wholeObjectSha256
  requestedStart
  requestedEndExclusive
  preconditionMode = STRONG_IF_RANGE | IMMUTABLE_GENERATION_PRECONDITION
  allowWholeObjectFallback
  expiresAtEpochMs

adapter-owned identity vault에는 다음 data-plane binding을 함께 둔다.

exact HTTPS URL/query
exact GET method
exact origin/path
exact Range header
exact precondition header/value selected by preconditionMode
required response headers
allowed statuses = policy-derived exact subset of 200 | 206 | 412 | 416
expected representation binding
maximum response bytes
single-use receipt

representationBindingSha256는 protocol/version, logical resource, immutable generation, precondition mode별 normalized strong validator 또는 generation binding, exact total length, media type와 expected whole-object digest의 canonical binding이다. 이것은 authorization proof가 아니다. BFF는 client 값을 echo하지 않고 registry snapshot에서 직접 재계산한다.

5.2 Strong validator

resume에는 다음 중 하나가 필요하다.

  • server registry가 소유하는 immutable object generation과 그 generation에 pin된 proxy/direct request
  • RFC semantics를 만족하는 strong ETag와 exact If-Range

weak ETag(W/), Last-Modified만 있는 representation, multipart ETag를 whole digest로 해석한 값과 CDN이 임의로 다시 쓴 validator는 resume authority로 사용하지 않는다. provider가 strong validator를 제공하지 못하면 BFF가 immutable generation을 pin하거나 Range resume를 UNSUPPORTED로 닫는다.

raw ETag와 provider generation locator는 application, checkpoint, diagnostics와 telemetry에 노출하지 않는다. reload 뒤에는 BFF가 새 capability를 발급하고, runtime은 새 capability의 representationBindingSha256가 checkpoint와 같은지 확인한 뒤 vault 안의 exact precondition만 사용한다.

STRONG_IF_RANGE mode는 exact If-Range를 보내고 206, Range-ignore 또는 validator mismatch의 full 200과 해당 416만 계약한다. IMMUTABLE_GENERATION_PRECONDITION mode는 BFF/provider가 정한 exact If-Match 또는 generation precondition을 보내며 412를 계약할 수 있다. allowWholeObjectFallbackallowedStatuses는 mode, requested start와 provider topology에서 capability 발급 시 닫히며 executor가 임의로 넓히지 않는다.

5.3 Capability 재발급

capability expiry, data-plane 401/403/410 또는 최소 잔여 lifetime 부족은 같은 URL의 무조건 retry가 아니다.

  1. 현재 response reader를 cancel하고 capability를 consume한다.
  2. control plane에 downloadKey, resource와 expected representation binding, exact next range를 전달한다.
  3. BFF가 authorization와 current generation을 다시 읽는다.
  4. binding이 같을 때만 새 capability로 같은 range를 재시도한다.
  5. binding이 바뀌었으면 partial destination을 append하지 않고 REPRESENTATION_CHANGED/RESTART로 닫는다.

재발급 횟수, 전체 operation deadline과 retry backoff는 composition hard ceiling 안에 둔다. capability를 durable queue나 worker message에 저장하지 않는다.

6. Durable checkpoint

6.1 Schema

RangeDownloadCheckpointV1
  schemaVersion = 1
  protocol = RANGE_RESUMABLE_DOWNLOAD_V1
  revision
  state = ACTIVE | PAUSED | FINALIZING | CLEANUP_PENDING
  downloadKey
  resourceBindingSha256
  representationBindingSha256
  totalByteLength
  nextOffset
  committedSegmentCount
  destination
    kind = OPFS_STAGING | SEEKABLE_FILE
    opaqueDestinationBinding
  createdAtEpochMs
  updatedAtEpochMs
  retentionExpiresAtEpochMs

downloadKey, destination binding과 physical database/OPFS namespace는 composition-issued opaque token이다. 사용자 file name, resource ID, account ID, tenant ID 또는 local path를 넣지 않는다.

checkpoint에 금지하는 값:

  • presigned URL, query와 signed request/response header
  • bearer/session/auth/CSRF token
  • raw ETag, provider object key/generation locator
  • file name, user path와 native exception
  • incremental hash 내부 state
  • raw backend response나 retry body

허용된 digest binding과 offset은 비권한성 recovery metadata다. account partition, retention, count/byte budget과 logout deletion을 적용하며 log/analytics/ticket에는 내보내지 않는다.

6.2 CAS와 durable offset

downloadKey는 cross-context exclusive mutation lock으로 직렬화한다. lock은 correctness의 유일한 authority가 아니며 checkpoint revision CAS와 exact destination binding이 최종 local authority다.

nextOffset은 다음 순서가 모두 성공한 뒤에만 전진한다.

  1. exact 206 range를 bounded stream으로 읽는다.
  2. expected start 위치에만 쓴다.
  3. writer close/segment commit을 완료한다.
  4. destination의 committed length가 expected end 이상인지 확인한다.
  5. unexpected tail이 있으면 authorized truncate(expectedEnd)를 완료한다.
  6. checkpoint를 revision + 1, nextOffset = expectedEnd로 CAS한다.

response가 성공했지만 destination commit 전에 crash하면 checkpoint는 이전 offset에 머문다. 재시작은 destination을 checkpoint offset으로 truncate하고 같은 range를 다시 요청한다. destination commit 뒤 checkpoint CAS가 유실된 경우도 동일하게 checkpoint offset까지 truncate한 뒤 재전송한다. 따라서 중복 byte를 append하지 않는다.

6.3 Inventory와 retention

checkpoint store는 단일 key read 외에 bounded admin operation을 제공해야 한다.

  • account partition 안의 safe summary를 cursor page로 list
  • expired/terminal checkpoint를 bounded batch로 classify
  • destination binding과 함께 exact owned staging을 cleanup
  • active lock/lease가 있는 항목은 건너뜀
  • count, logical bytes, maximum age와 cleanup retry budget 강제
  • cleanup receipt를 durable하게 남기고 response 유실을 reconcile

inventory에는 resource ID, file name, digest, raw validator와 path를 반환하지 않는다. 제품 resume UI가 필요한 경우 제품 database/query가 별도 safe display metadata를 소유하고 opaque downloadKey로만 연결한다.

7. Destination 계약

7.1 공통 port

ResumableDownloadDestinationPort
  inspect(binding) -> committedLength, readable, writable, permissionState
  openWriter(binding, keepExistingData=true)
  seek(offset)
  write(chunk)
  truncate(length)
  commitSegment()
  openReader(start=0)
  finalize()
  abortAttempt()
  cleanup(authority)

native handle, OPFS handle와 path는 adapter 밖으로 노출하지 않는다. 모든 method는 bounded deadline, AbortSignal과 closed failure를 사용한다.

7.2 Seekable external file

직접 외부 파일에 resume하려면 browser가 기존 data 보존, seek, truncate, 재읽기와 permission 재확인을 실제로 지원해야 한다.

  • picker와 permission request는 Window의 명시적 user activation에서만 실행한다.
  • structured-cloned handle을 보존하는 경우 별도 privacy/retention 승인이 필요하다.
  • reopen 뒤 queryPermission/requestPermission을 거치며 denied면 PERMISSION_DENIED/RESELECT다.
  • writer가 temporary-file commit semantics를 쓰면 segment마다 close한 뒤 committed file size를 다시 확인한다.
  • checkpoint보다 큰 tail은 검증하지 않고 사용하지 않으며 exact checkpoint offset으로 truncate한다.
  • checkpoint보다 파일이 작거나 다른 handle이면 CONFLICT/RESTART다.

브라우저가 이 계약을 만족하지 못하면 external-file resume를 흉내 내지 않고 OPFS staging 또는 browser-managed handoff로 전환한다.

7.3 OPFS staging

cross-browser app-controlled resume의 우선 destination은 policy-owned OPFS staging이다.

  • physical path는 기존 OPFS authority/namespace/partition registry가 발급한다.
  • checkpoint와 OPFS object는 immutable binding과 generation journal로 연결한다.
  • quota estimate는 admission hint일 뿐이며 write 중 quota failure도 처리한다.
  • download 완료 뒤 staging 전체를 다시 읽어 SHA-256을 검증한다.
  • foreground user activation에서 새 외부 destination을 열고 staging을 stream export한다.
  • 외부 export close가 성공하기 전 staging을 삭제하지 않는다.
  • export 결과가 유실되면 staging을 유지하고 user에게 retry 가능한 상태를 반환한다.

OPFS 저장 성공은 사용자가 접근 가능한 파일 저장 완료가 아니다. 결과를 STAGED_VERIFIEDSAVED_VERIFIED로 구분한다. OPFS는 큰 파일에서 storage와 I/O를 한 번 더 요구하므로 quota/retention owner 없는 기본 fallback이 아니다.

8. HTTP 상태 머신

8.1 요청 전

  1. checkpoint와 destination binding을 exact하게 읽는다.
  2. destination length를 검사하고 checkpoint보다 큰 tail을 truncate한다.
  3. checkpoint보다 작으면 partial을 신뢰하지 않고 restart/cleanup으로 닫는다.
  4. 새 capability의 representation binding과 exact range를 검증한다.
  5. Range: bytes=S-E와 capability의 preconditionMode가 정한 exact If-Range 또는 immutable-generation precondition을 vault binding 그대로 보낸다.
  6. credentials: omit, redirect: error, no-referrer, no-store, identity content encoding을 강제한다.

한 request의 range 크기와 exact S/E는 capability 발급 전에 composition maximum 안에서 계산한다. executor는 capability의 requestedStart, requestedEndExclusive와 exact Range header가 일치하는지 검증하고 그대로 전송하며 다시 줄이거나 늘리지 않는다. ceiling을 넘는 capability는 사용 전에 거절한다. 기본 protocol은 sequential range만 허용한다. parallel range와 sparse destination은 별도 protocol/version 없이는 사용하지 않는다. zero-byte representation은 유효하지 않은 byte range를 만들지 않는다. exact length가 0이고 empty-object SHA-256 binding이 일치하는 whole-object 200 경로로 body/length를 확인한 뒤 바로 final verification으로 이동한다.

response body를 읽거나 destination writer를 열기 전에 200, 206, 412, 416 중 수신한 status가 capability vault의 exact allowedStatuses member인지 검사한다. 해당 네 값 중 허용되지 않은 status는 body를 cancel하고 capability를 consume하며 destination과 checkpoint를 변경하지 않은 채 CONTRACT_MISMATCH로 fail-closed한다. 아래 네 분기는 이 공통 admission gate를 통과한 경우에만 실행한다. 그 밖의 status는 §8.6의 별도 failure/reissue 규칙으로 처리한다.

8.2 206 Partial Content

성공 조건:

  • 206이 capability의 allowedStatuses member
  • final response URL이 capability URL과 exact match
  • Content-Range: bytes S-E/T가 하나만 존재하고 parse가 엄격함
  • S가 requested start, E + 1이 requested end exclusive
  • T가 checkpoint total과 같음
  • Content-Length = E - S + 1
  • strong validator/immutable generation binding 일치
  • media type과 identity encoding 일치
  • body 실제 bytes가 exact content length

하나라도 다르면 reader와 current destination attempt를 abort하고 checkpoint를 전진시키지 않는다. 정상인 경우에만 앞 절의 durable offset 순서로 commit한다.

8.3 200 OK

200은 capability의 allowedStatuses member인 경우에만 이 분기로 들어온다. body를 destination에 쓰기 전에 final response URL, required response header, media type, identity encoding과 mode별 strong validator 또는 immutable-generation evidence가 capability의 exact representation binding과 일치하는지 검증한다.

다음 순서로 배타적으로 처리한다.

  1. validator/generation evidence가 없거나 binding이 다르면 body를 cancel하고 capability를 consume한다. 기존 checkpoint와 partial은 append하지 않고 quarantine/retention policy로 전환한 뒤 control plane에서 current representation을 다시 확인한다. 결과는 REPRESENTATION_CHANGED/RESTART이며 byte 0의 새 operation만 허용한다.
  2. binding은 같지만 requested start가 0이고 allowWholeObjectFallback=true이면 fresh whole-object destination에서 기존 whole-object stream 계약으로 처리한다. exact total length와 final whole-object digest를 검증하기 전에는 success나 final commit을 반환하지 않는다.
  3. binding은 같고 requested start가 0이지만 allowWholeObjectFallback=false이면 body를 한 byte도 쓰지 않고 cancel한다. 결과는 WHOLE_OBJECT_FALLBACK_NOT_ALLOWED이며 policy가 허용한 새 Range capability, browser handoff 또는 explicit unsupported만 선택한다.
  4. binding은 같고 requested start가 0보다 크면 server가 Range를 무시한 것이다. body를 한 byte도 쓰지 않고 cancel하며 기존 partial을 같은 writer에서 덮어쓰지 않는다. control plane reconcile 뒤 같은 representation의 byte 0 restart operation, browser handoff 또는 explicit unsupported만 선택한다.

8.4 412 Precondition Failed

412가 capability의 allowedStatuses member이고 preconditionMode=IMMUTABLE_GENERATION_PRECONDITION인 경우에만 이 분기로 들어온다. representation precondition 실패다. body를 cancel하고 checkpoint를 유지한 채 control plane에서 current generation을 확인한다. 같은 binding을 다시 발급하지 못하면 partial은 cleanup policy에 따라 폐기하고 byte 0부터 새 operation을 시작한다.

RFC If-Range validator mismatch 자체의 정상 응답은 200이다. 412는 BFF나 provider가 immutable generation을 pin하기 위해 별도 If-Match 계열 precondition을 함께 강제하는 topology에서만 이 상태 머신에 들어온다. topology가 412를 계약하지 않았다면 unknown status로 fail-closed한다.

8.5 416 Range Not Satisfiable

416이 capability의 allowedStatuses member인 경우에만 이 분기로 들어온다. response body는 download data로 소비하지 않고 cancel한다. Content-Range: bytes */T를 strict하게 검사하며 final response URL, required headers와 mode별 validator/generation binding도 확인한다. provider의 416이 binding evidence를 반환할 수 없는 topology라면 BFF control plane reconcile이 exact immutable generation을 다시 증명하기 전에는 EOF나 missing-range 분기로 진행하지 않는다.

다음 순서를 사용하며 한 분기를 처리한 뒤 아래 분기로 fall through하지 않는다.

  1. malformed/missing T, final URL/header mismatch 또는 증명되지 않은 representation binding은 CONTRACT_MISMATCH로 fail-closed한다.
  2. T != expected total이면 representation changed다. local bytes를 T에 맞춰 자동 truncate하거나 append하지 않고 capability를 consume한 뒤 partial을 quarantine/restart한다.
  3. nextOffset > T이면 checkpoint 자체가 corrupt/stale이다. 잘못된 offset으로 truncate하지 않고 checkpoint와 partial을 quarantine한 뒤 restart/recovery로 닫는다.
  4. nextOffset <= T이지만 local committed length != nextOffset이면 먼저 local state를 reconcile한다.
    • local length가 더 크면 exact nextOffset까지만 uncommitted tail을 authorized truncate하고 durable length를 다시 확인한다.
    • local length가 더 작으면 journal이 증명하는 마지막 confirmed segment로 destination과 checkpoint를 함께 CAS rollback할 수 있을 때만 복구한다. 그렇지 않으면 quarantine/restart한다. 이 분기는 reconcile 결과를 새 state-machine invocation에서 다시 평가하며 바로 finalization이나 missing-range request로 진행하지 않는다.
  5. local committed length == nextOffset == T이면 data transfer가 끝난 후보로 보고 FINALIZING whole-object verification으로 이동한다.
  6. local committed length == nextOffset < T이면 local missing range가 남아 있다. 새 capability로 exact nextOffset range를 재발급한다. 같은 total에 대해 satisfiable range가 다시 416이면 bounded retry하지 않고 CONTRACT_MISMATCH로 fail-closed한다.

416 자체를 다운로드 성공으로 간주하지 않는다.

8.6 나머지 상태와 network failure

조건 처리
401/403/410 bounded capability reissue; binding mismatch면 restart
404 existence-hiding policy에 따라 unavailable/not-found, partial cleanup 예약
409 server representation/session reconcile
429/모든 5xx/network 동일 exact range만 bounded retry
redirect/opaque response policy rejection
timeout/cancel reader와 writer attempt abort, checkpoint 유지
overrun/truncation integrity failure, checkpoint 유지

retry는 destination commit 여부를 먼저 판단한다. effect가 ambiguous하면 checkpoint와 destination length를 reconcile하기 전 새 offset으로 이동하지 않는다.

9. Whole-object integrity와 final commit

Range별 transport 검증은 whole-object 무결성 증거가 아니다. 모든 bytes가 수신되면 checkpoint를 FINALIZING으로 CAS하고 다음을 수행한다.

  1. destination length가 exact total과 같은지 확인한다.
  2. destination을 byte 0부터 bounded chunk로 다시 읽는다.
  3. vetted incremental SHA-256으로 whole-object digest를 계산한다.
  4. capability/representation binding의 expected digest와 constant-time 비교한다.
  5. mismatch면 사용자 destination을 성공으로 표시하지 않고 staging을 격리하거나 authorized cleanup한다.
  6. OPFS staging이면 foreground external export와 destination close를 완료한다.
  7. final destination commit truth를 확인한 뒤만 SAVED_VERIFIED를 반환한다.
  8. checkpoint와 staging cleanup을 exact revision/receipt로 완료한다.

portable하지 않은 incremental hash 내부 state를 checkpoint에 serialize하지 않는다. 마지막 full reread 비용을 피하려면 chunk digest/Merkle manifest를 별도 protocol로 설계하고 server가 exact proof를 제공해야 한다.

10. Pause, cancel, crash와 account lifecycle

10.1 Pause

pause(downloadKey)는 browser work 중단이며 server resource/capability revoke가 아니다.

  • 같은 runtime의 read/write/backoff를 AbortSignal로 중단한다.
  • same-origin context에는 opaque key만 담은 versioned ephemeral pause event를 보낸다.
  • mutation lock 안에서 checkpoint를 PAUSED로 CAS한다.
  • in-memory URL/header/capability는 즉시 retire한다.
  • committed segment는 유지하고 ambiguous writer attempt는 checkpoint offset으로 reconcile한다.

10.2 Cancel과 discard

cancel은 transfer 중단만 의미할 수 있고, discard는 local partial 삭제다. 제품 facade가 두 의도를 구분해야 한다. discard는 exact partition/destination binding과 short-lived maintenance authority를 요구하며 checkpoint와 OPFS staging을 하나의 cleanup journal로 처리한다.

10.3 Crash/reload

reload 후 runtime은:

  1. account partition과 governance binding을 검증한다.
  2. checkpoint schema/protocol/revision을 검증한다.
  3. destination을 reopen하고 permission/length를 검사한다.
  4. server에서 새 capability를 발급받아 representation binding을 대조한다.
  5. exact checkpoint offset부터 resume한다.

source가 같은지 사용자에게 묻는 file-name 기반 확인은 사용하지 않는다.

10.4 Logout/account/tenant switch

  • 신규 capability 발급과 resume admission을 먼저 닫는다.
  • active foreground operation을 abort하고 writer를 정리한다.
  • vault와 worker channel을 close한다.
  • account partition의 checkpoint와 owned staging을 maintenance-authorized bounded cleanup으로 제거한다.
  • blocked deletion을 성공으로 보고하지 않는다.
  • 이전 account handle/reference를 새 runtime에서 resolve하지 않는다.

retention/legal-hold 정책이 local partial 보존을 요구하는 특별한 제품이 아니라면 logout에서 partial을 제거하는 것이 기본이다.

11. Download strategy selector

selector는 presentation의 임의 조건문이 아니라 composition-owned immutable policy와 runtime probe를 받는 공통 application service다.

입력:

  • source가 server resource인지 client-generated artifact인지
  • exact 또는 maximum byte length
  • verified integrity 필요 여부
  • resume/background 요구
  • system save picker, seek/truncate, OPFS와 worker capability
  • user activation
  • storage quota admission
  • browser-managed capability availability
  • data classification와 retention policy

결과:

조건 선택
server file, 탭 종료 뒤 계속 필요 BROWSER_MANAGED_HANDOFF
server file, verified foreground save, picker 지원 WHOLE_OBJECT_PICKER_STREAM
server file, resume 필수, destination 계약 충족 RANGE_RESUMABLE_FOREGROUND
작은 generated artifact BOUNDED_OBJECT_URL
큰 generated artifact, picker 지원 WHOLE_OBJECT_PICKER_STREAM
큰 generated artifact, picker 미지원 SERVER_GENERATION_REQUIRED 또는 UNSUPPORTED
app background download가 승인·지원되고 OPFS quota 확보 APP_MANAGED_BACKGROUND_DOWNLOAD

selector는 fallback으로 byte/memory/security ceiling을 올리지 않는다. integrity가 필수인데 browser handoff만 가능하면 “검증된 저장”으로 downgrade하지 않고 제품이 handoff 또는 unsupported 중 하나를 명시적으로 선택한다.

Safari와 picker 미지원 환경

user-agent 문자열로 Safari를 판별하지 않는다. 필요한 API와 실제 semantics를 capability probe로 확인한다.

  • 대용량 server file: authorized Content-Disposition browser handoff
  • 작은 generated file: bounded Blob/object URL
  • 대용량 generated file: server-side generation 또는 unsupported
  • OPFS: app-private staging일 뿐 Finder/Files 저장 완료로 표시하지 않음
  • system save picker 미지원: unbounded Blob으로 자동 전환하지 않음
  • seek/truncate/permission semantics 미충족: external Range resume 비활성화

browser-managed handoff endpoint는 cross-origin download attribute에 의존하지 않고 server가 safe Content-Disposition, media type, byte/generation policy를 실제 response에서 강제한다.

12. Background download 전달의 세 의미

12.1 Foreground app-managed

page가 열린 동안 runtime이 fetch, progress, integrity와 destination을 모두 관리한다. 현재 whole-object stream과 목표 Range resume가 이 범주다. page lifecycle 종료 뒤 지속을 보장하지 않는다.

12.2 Browser-managed handoff

navigation/download manager에 authorized endpoint를 넘긴다.

  • page 종료 뒤 계속될 수 있는 가장 넓은 fallback
  • application은 실제 disk write, 저장 위치와 final digest를 관찰하지 못함
  • 결과는 BROWSER_HANDOFF, SAVEDVERIFIED가 아님
  • pause/resume UI와 retry semantics는 browser가 소유

12.3 App-managed background download

Service Worker/Background Fetch 등에서 application이 progress/retry/staging을 관리하려는 별도 optional capability다.

필수 조건:

  • target browser/deployment의 explicit support matrix
  • worker-safe authenticated control plane
  • worker가 매 range마다 새 short-lived capability를 발급받는 계약
  • capability/URL/header를 IDB, OPFS, Cache Storage와 message에 저장하지 않음
  • private bytes는 Cache Storage가 아니라 policy-owned OPFS staging 사용
  • worker termination을 정상 상태로 보고 checkpoint에서 재개
  • concurrency, battery/network, quota와 retention ceiling
  • logout/revocation event와 worker admission fence
  • client/worker version compatibility와 upgrade drain
  • notification/foreground export UX

일반 Service Worker의 수명이나 background execution 시간을 correctness 근거로 삼지 않는다. Background Fetch가 없는 환경에서 timer/keepalive로 장기 download를 흉내 내지 않는다. user-visible external save picker는 worker에서 호출하지 않고 완료된 OPFS staging을 다음 foreground user gesture에서 export한다.

따라서 app-managed background download가 향후 AVAILABLE_NOT_COMPOSED 또는 COMPOSED가 되더라도 지원 browser의 progressive enhancement일 뿐이다. cross-browser 보장 자체의 primary status는 계속 PLATFORM_LIMITED다.

13. Security, privacy와 observability

  • URL/query/header, validator와 capability는 bearer 또는 sensitive metadata로 취급한다.
  • RangepreconditionMode가 선택한 exact If-Range 또는 immutable-generation precondition은 adapter vault가 binding 그대로 생성한다.
  • caller는 offset을 늘리거나 arbitrary range를 요청하지 못한다.
  • account partition과 resource authorization을 매 capability reissue에서 검사한다.
  • partial bytes는 원본과 같은 data classification, retention, encryption-at-rest와 deletion policy를 적용한다.
  • OPFS quota pressure가 다른 account partial을 제거할 권한을 주지 않는다.
  • preview, execution 또는 Cache Storage promotion은 final verification 전 금지한다.
  • high-cardinality ID, file name, path, URL, raw ETag와 digest를 metric label/log에 넣지 않는다.

허용된 aggregate observation:

  • strategy와 destination kind
  • response state bucket
  • expected/committed byte bucket
  • retry/reissue/resume count bucket
  • duration, pause, restart, integrity와 cleanup outcome
  • browser capability support reason code

14. Composition과 operational admission

createBrowserTransferRuntime에 해당하는 미래 composition owner만 다음을 조합한다.

  • versioned wire codecs와 fixed BFF endpoint
  • capability vault/provider/executor
  • Range checkpoint store, destination registry와 mutation lock
  • selector policy와 browser capability probe
  • presigned, upload, image와 Range lifecycle
  • account/logout cleanup authority
  • safe observer
  • traffic admission과 kill switch

독립적인 canonical readiness 상태:

Selection         = NOT_SELECTED | SELECTED | REMOVING
TrafficAdmission  = DISABLED | SHADOW | CANARY | ENABLED
RuntimeHealth     = UNKNOWN | AVAILABLE | DEGRADED | UNAVAILABLE | INCOMPATIBLE
PromotionEvidence = MISSING | PARTIAL | COMPLETE | EXPIRED

primary status가 COMPOSED여도 TrafficAdmission 기본값은 DISABLED다. 필수 config, strong validator/provider conformance, destination semantics, cleanup owner 또는 valid evidence가 없으면 TrafficAdmission=DISABLED, RuntimeHealth=UNKNOWN | UNAVAILABLE, PromotionEvidence=MISSING | PARTIAL | EXPIRED로 readiness를 fail-closed한다. 이미 승인된 product selection 자체를 provider evidence 부족만으로 되돌리지 않는다.

Kill switch:

  • 신규 Range capability issuance off
  • Range resume off → whole-object restart 또는 browser handoff
  • direct object-store Range off → BFF proxy
  • external seek destination off → OPFS staging 또는 handoff
  • app-managed background download off → foreground/browser handoff
  • final export off → verified staging 유지

kill switch는 partial을 자동 삭제하거나 handoff를 saved/verified로 바꾸지 않는다.

15. Test와 conformance matrix

15.1 Deterministic runtime

  • checkpoint CAS conflict와 corrupt/unknown field
  • exact segment commit 전/후 crash
  • destination larger/smaller/different binding
  • pause/resume/cancel/discard races
  • capability expiry/reissue와 representation change
  • 200/206/412/416 모든 분기
  • malformed/multiple/overflow Content-Range
  • weak/missing/mismatched validator
  • overrun, truncation, stalled body와 abort
  • final whole-object digest mismatch
  • cleanup response loss와 replay
  • count/byte/age retention sweep

15.2 Browser matrix

  • system picker 지원/미지원
  • seek/truncate/keep-existing-data semantics
  • OPFS quota, eviction, reload와 worker termination
  • cross-tab lock/pause delivery
  • user activation과 permission denied/revoked
  • large server handoff
  • foreground export close/abort
  • Chromium, Firefox와 WebKit 동일 필수 case set

지원하지 않는 API는 skip이 아니라 selector의 expected fallback/UNSUPPORTED 결과로 검증한다.

15.3 BFF/object provider contract

  • immutable generation pin
  • capability preconditionMode에 따른 strong If-Range 또는 immutable generation precondition
  • beginning/middle/end/empty/invalid range
  • exact 206 Content-Range와 length
  • deliberate Range ignore 200
  • mode가 계약한 경우의 precondition 412, EOF/invalid 416
  • mid-transfer capability expiry/revocation
  • redirect/CORS/exposed-header/identity-encoding
  • object replacement race
  • direct provider와 proxy 결과 동등성
  • URL/header/log redaction

fake와 route interception은 actual provider conformance를 대체하지 않는다.

15.4 Background-download-specific fault

  • worker가 range commit 전/후 종료
  • worker/client version 교체
  • logout과 capability revocation
  • offline/online 반복, quota exhaustion과 battery/network policy
  • notification 유실과 foreground export replay
  • unsupported browser가 foreground/handoff로 정확히 fallback

16. Rollout과 promotion

Primary status 변경과 readiness/traffic promotion은 별도로 승인한다.

  1. Range의 DESIGNED_NOT_IMPLEMENTED와 ADR-local sourceEvidence=DESIGN_REVIEWED를 확인한다.
  2. provider-neutral ports/runtime, deterministic fake, negative fixture와 browser test를 완성한 경우에만 Range primary status를 AVAILABLE_NOT_COMPOSED, source evidence를 REFERENCE_TESTED로 변경한다. deterministic/reference test만으로 canonical PromotionEvidenceCOMPLETE로 바꾸지 않는다.
  3. 제품 요구, owner, data class와 fallback이 선택되지 않은 app-managed background download는 계속 NOT_SELECTED로 둔다. cross-browser 보장은 PLATFORM_LIMITED다.
  4. fixed staging BFF/provider, actual config, account lifecycle와 runbook을 설치한 capability만 COMPOSED로 기록한다. 이때도 TrafficAdmission=DISABLED, RuntimeHealth=UNKNOWN, PromotionEvidence=PARTIAL이다.
  5. operator probe와 shadow에서 contract evidence를 수집한다.
  6. internal cohort에서 BFF proxy Range를 먼저 canary한다.
  7. direct provider Range와 external seek destination은 각각 별도 canary한다.
  8. app-managed background download를 실제로 선택했다면 지원 browser cohort에서만 별도 opt-in canary한다.
  9. contract/provider/browser/operations component gate, SLO, cleanup drill, rollback과 evidence freshness가 모두 충족된 승인 범위만 PromotionEvidence=COMPLETE, RuntimeHealth=AVAILABLE, TrafficAdmission=ENABLED로 promotion한다.

provider, endpoint, validator semantics, browser major behavior, destination adapter, wire protocol 또는 security policy가 바뀌면 relevant evidence를 만료시키고 재승인한다.

17. Rollback과 제거

운영 rollback 순서:

  1. 신규 Range/background-download admission과 capability 발급을 중지한다.
  2. active writer/worker를 abort하고 checkpoint offset으로 reconcile한다.
  3. app background download를 foreground/browser handoff로 낮춘다.
  4. direct Range를 BFF proxy 또는 whole-object restart로 낮춘다.
  5. verified OPFS staging은 retention window 안에서 foreground export 가능 상태로 유지한다.
  6. ambiguous partial은 성공으로 표시하지 않고 cleanup queue로 넘긴다.
  7. provider/signing credential 노출이 원인이면 backend revoke와 key rotation을 수행한다.

완전 제거:

  1. pending checkpoint/staging inventory를 bounded하게 drain, export 또는 discard한다.
  2. worker, channel, lock과 runtime을 close한다.
  3. account-partition checkpoint/OPFS namespace를 maintenance-authorized cleanup한다.
  4. endpoint, worker registration, config, policy와 feature facade를 제거한다.
  5. production bundle/module inventory와 removal test로 source 부재를 증명한다.

rollback은 unbounded Blob fallback, validator 완화, digest 생략 또는 partial 자동 append를 허용하지 않는다.

18. 완료 기준

Range resumable download는 다음이 모두 참일 때만 구현 완료다.

  • RANGE_RESUMABLE_DOWNLOAD_V1 port와 strict wire codec이 있음
  • capability mode별 exact allowed-status subset과 200/206/412/416 처리 상태 머신이 실행 가능하게 검증됨
  • strong validator/immutable generation이 실제 provider에서 강제됨
  • checkpoint CAS, inventory, retention과 account cleanup이 구현됨
  • seek/truncate 또는 OPFS staging destination이 crash fault를 통과함
  • capability reissue가 representation mismatch를 fail-closed함
  • final whole-object SHA-256 뒤에만 verified success를 반환함
  • selector가 picker/seek 미지원과 대용량 fallback을 안전하게 결정함
  • actual BFF/provider와 Chromium/Firefox/WebKit evidence가 유효함
  • SLO, alert, runbook, kill switch, rollback과 cleanup drill이 승인됨

app-managed background download는 위 항목에 더해 다음이 필요하다.

  • 지원 browser/deployment 범위가 명시됨
  • worker lifecycle 종료를 checkpoint로 복구함
  • worker control plane이 durable capability 저장 없이 동작함
  • logout/revocation/version upgrade fault가 통과함
  • 미지원 browser fallback이 동일 제품 요구를 안전하게 만족하거나 명시적 unsupported UX를 가짐

이 기준 전에는 기존 foreground streaming 또는 browser handoff의 성공을 Range나 background download 구현 완료 증거로 사용하지 않는다.

19. 선택하지 않은 대안

  • Range header만 추가하고 기존 sequential writable에 append
  • weak ETag나 file name/lastModified를 representation identity로 사용
  • serialized incremental hash state를 검증 없이 checkpoint
  • 200 response를 기존 partial 뒤에 append
  • 416을 곧바로 success로 해석
  • Service Worker keepalive를 cross-browser background 보장으로 간주
  • picker 미지원 대용량 파일을 unbounded Blob으로 fallback
  • OPFS staging을 사용자 파일 저장 완료로 표시
  • browser-managed handoff를 application-verified save로 표시
  • user-agent 문자열 기반 Safari 분기

20. 참고