chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
@@ -0,0 +1,202 @@
# VD-12: Presigned transfer, resumable upload와 Image CDN 경계
- 상태: Accepted — reference runtime available, not composed
- 결정일: 2026-07-28
- catalog recipe availability: `RECIPE_AVAILABLE` (primary status/selection과 별도)
- reference runtime 상태: `AVAILABLE_NOT_COMPOSED`
- 관련 결정: VD-10, VD-11, VD-14, VD-16
- current status ledger:
`docs/architecture/browser-data-capability-completion-ledger.md`
- 재검토: 제품이 server file upload/download 또는 Image CDN delivery를 선택할 때
## 배경
Presigned URL은 URL 문자열이 아니라 짧은 수명의 bearer capability다.
multipart/resumable upload는 단순 PUT 반복이 아니라 session, part identity,
checksum, authoritative reconciliation, completion과 orphan cleanup protocol이다.
streaming download는 전체 payload를 메모리에 올리지 않지만 response binding,
truncation/overrun, destination commit과 integrity를 별도로 처리해야 한다.
Image CDN URL도 arbitrary transform builder로 노출하면 cache poisoning, pixel/decode
bomb, signed-query 유출과 source-fetch SSRF 경계가 사라진다.
이 네 capability를 범용 `HttpClient``FileService` mega-port 하나로 합치면
control plane authorization과 byte data plane, local browser lifecycle과 server
authority가 섞인다.
## 결정
1. BFF/Web API control plane과 object-storage/CDN data plane을 분리한다.
2. 브라우저는 signing key, cloud 관리자 credential, bucket/container, raw object
key 생성 규칙을 소유하지 않는다.
3. application caller는 raw URL/query/signed headers를 전달하지 않는다.
composition/provider가 발급한 exact capability만 adapter가 소비한다.
4. presigned capability는 version, opaque identity, method, logical resource 또는
session/part, exact URL, origin/path policy, byte/media/checksum 조건과 expiry를
immutable하게 binding한다.
5. signed URL은 bearer credential로 취급하고 persistence, checkpoint, telemetry,
analytics, referrer와 raw exception에 넣지 않는다.
6. data-plane fetch는 기본적으로 `credentials: omit`, `redirect: error`,
`referrerPolicy: no-referrer`, `cache: no-store`를 사용한다. cross-origin은
composition allowlist와 CORS/CSP 계약이 있을 때만 연다.
7. client-side single-use 표시는 UX와 accidental replay를 줄이는 보조책이다.
cross-tab/replay의 최종 authority는 server 또는 composition-owned atomic
consumer다.
8. streaming download는 response body를 closed-result stream으로 변환하고
output chunk, total bytes, media type, encoding과 선택적 incremental integrity를
검증한다. overrun/truncation/abort 시 native reader와 destination을 닫는다.
9. `BROWSER_HANDOFF`와 destination close 이후의 `SAVED`를 계속 분리한다.
10. Range resumable download는 별도 capability다. `206`, `Content-Range`,
validator, destination seek/truncate와 final integrity 없이는 append resume를
허용하지 않는다.
11. upload 상위 계약은 server-authoritative session/status/part/complete/abort를
소유하고 data-plane part executor는 capability 타입에 generic하다. 따라서
S3-style presigned multipart와 BFF proxy part를 같은 application contract
뒤에 둘 수 있지만 wire DTO를 공유하지 않는다.
12. reference upload protocol literal은 `PRESIGNED_MULTIPART_V1`이다. 모든
control-plane request/response, session과 checkpoint가 이를 exact하게
포함하며 다른 값이나 누락을 거절한다.
13. session은 exact source binding, total bytes, media type, part size/count,
concurrency, checksum algorithm과 expiry를 묶는다. part number는 1부터
연속적이고 offset/length/checksum/idempotency를 정확히 binding한다.
14. control transport는 `CREATE_SESSION`, `GET_STATUS`, `COMPLETE`, `ABORT`
closed operation을 composition-owned fixed HTTPS endpoint map으로만
실행한다. presigned 발급도 factory에 고정된 단일 BFF endpoint를 사용하며
caller-provided URL을 받지 않는다.
15. `requestBindingSha256``uploadBindingSha256`
`RESUMABLE-UPLOAD-BINDING-V1`
`RESUMABLE-UPLOAD-SESSION-BINDING-V1` canonical field sequence의 SHA-256이다.
`UPLOAD_PART` capability binding은 exact
`protocol: PRESIGNED_MULTIPART_V1`을 포함한다. BFF는 `sessionId`로 server
session을 조회하고 snapshot으로 protocol, binding과 part plan을 재계산한다.
client digest는 authorization이나 ownership 증명이 아니다.
16. part memory는 `partSize × concurrency × copyFactor` hard ceiling으로 제한한다.
retry는 같은 bytes/checksum/idempotency에만 허용한다.
17. retryable network, 429와 모든 5xx는 bounded attempt/`Retry-After`/abortable
backoff 안에서만 재시도한다. status의 404/410 또는
`NOT_FOUND`/`EXPIRED`는 terminal로 보고 checkpoint를 CAS 제거한다.
18. PUT 성공은 capability-bound status, receipt header,
`expectedResponseByteLength`와 exact `Content-Length`를 검증하고 hard cap과
deadline 안에서 response body를 끝까지 drain한 뒤에만 확정한다. 204는
expected response bytes가 0일 때만 허용하며 `Content-Length` 부재를 0으로
정규화한다.
19. resume는 local checkpoint만 신뢰하지 않는다. server status를 다시 읽고
완료 part의 local range digest와 server checksum/receipt를 대조한 뒤 missing
part만 전송한다.
20. checkpoint에는 opaque session/source binding과 reconciliation에 필요한
protocol-defined SHA-256 file fingerprint, per-part checksum, bounded opaque
non-authorizing part receipt token만 저장한다. 이 값도 account partition과
retention을 적용하고 diagnostics/telemetry에는 내보내지 않는다. presigned
URL, signed header, bearer token/capability, file name, path, account ID,
raw provider ETag와 raw server error는 금지한다.
21. cancel과 server abort를 분리한다. same-origin 다른 tab의 active upload는
strict `RESUMABLE_UPLOAD_CANCEL_V1` BroadcastChannel 신호로 먼저 중단한 뒤
per-key Web Lock 안에서 server abort/reconcile을 수행한다. 이 ephemeral
신호는 opaque upload key만 운반하고 persistence하지 않으며 authority가
아니다. channel이 없으면 abort caller의 bounded signal 아래 lock을 기다린다.
complete/abort가 불명확하면 server reconcile 전까지 성공으로 기록하거나
checkpoint를 파기하지 않는다.
22. multipart complete는 ordered receipt 검증 뒤에도 `QUARANTINED`다. backend
scan/CDR/promotion이 끝나기 전 available/public URL을 발급하지 않는다.
application-facing 성공값은 state/resource/byte length/replay 여부만 노출하고
session ID, request binding과 fingerprint를 제거한다.
23. Image CDN application contract는 opaque asset reference와
composition-registered named preset만 받는다. arbitrary source URL과 raw
transform query는 금지한다.
24. asset descriptor는 immutable revision, delivery class, safe raster media,
natural dimensions, rendition dimensions/formats/URLs와 private expiry를 묶는다.
25. CDN policy는 allowed HTTPS origin/path, preset width/DPR/format/quality/fit,
output pixel/decoded-byte/encoded-byte/candidate/lifetime ceiling과
cache/referrer policy를 소유한다.
composition limit은 exported adapter implementation ceiling을 초과할 수
없고 capability verification concurrency도 절대 상한 아래에서 제한한다.
CDN origin은 composition이 명시한 application origin과 달라야 한다.
`<img crossorigin="anonymous">`가 same-origin 요청에서는 cookie를 보낼 수
있기 때문에 private URL의 credential omission을 probe에만 맡기지 않는다.
26. private capability signature가 허용하는 값은 versioned preset binding ID다.
CDN/BFF는 그 ID를 server-owned immutable preset registry에서 조회하고,
요청의 width/height/DPR/fit/format/quality가 그 preset의 exact candidate인지
재계산해 하나라도 다르면 거절한다. signed URL에 붙은 raw transform query나
client 계산값은 authorization proof가 아니다.
signing key policy는 bounded unique `acceptedKeyIds` overlap set이고 verifier
registry가 모든 ID를 포함해야 한다. descriptor의 단일 key ID는 양쪽
registry에 exact membership이 있어야 한다.
27. browser probe는 native decode 전에 PNG/JPEG/WebP/AVIF header metadata와
static-only container를 검사한다. 선언 dimensions, pixels와 decoded-byte
budget을 넘거나 APNG/WebP animation, AVIF sequence/derived image,
ambiguous/malformed container이면 decode 전에 거절한다.
28. private signed delivery는 `PRIMARY_REQUIRED` probe를 강제하고
`credentials: omit`, exact response URL과 실제 `Cache-Control: no-store`
검증한다. fetch/body/decode 전체에 하나의 timeout을 적용하고 abort/late
completion에서 reader와 bitmap을 닫는다.
29. SVG/HTML/data/blob/javascript와 unknown active media는 기본 거절한다.
animation은 frame/decode budget이 승인된 별도 protocol 전에는 허용하지 않는다.
30. responsive candidate는 한 source set에서 하나의 descriptor 종류만 사용하고,
고유한 양수 width를 오름차순으로 반환한다. `sizes`는 registry-owned layout
token에서 결정한다.
31. public rendition은 immutable revision URL과 public immutable cache를 사용하고,
private rendition은 short-lived capability와 필수 no-store를
사용한다. 같은 URL의 content를 purge로 바꿔치기하지 않는다.
32. Image CDN runtime `close()`는 terminal/idempotent다. runtime lifetime
signal로 진행 중 verification/probe를 중단하고 accepted WeakMap을 새
WeakMap으로 교체해 기존 reference를 즉시 revoke한다. 닫힌 runtime은
재개하지 않고 새 composition으로 교체한다.
33. 공통 runtime은 concrete browser mechanism과 policy validation을 제공하지만
backend endpoint/vendor schema와 제품 asset/upload owner가 없으므로 bootstrap에
조합하지 않는다.
34. runtime source는 production module inventory와 removal gate로 기본 bundle에서
제외됨을 증명한다.
35. Range resume의 detailed state machine과 app-managed background의 플랫폼
경계는 VD-14가 소유한다. VD-12의 whole-object streaming 구현을 그
capability의 구현 증거로 사용하지 않는다.
36. top-level transfer runtime, account-scoped teardown, upload pause/inventory,
Image descriptor HTTP provider/refresh와 safe presentation projection은
VD-16이 소유한다. 개별 runtime factory의 존재를 operational composition
완료로 해석하지 않는다.
## Backend와 맞출 계약
- fixed BFF capability endpoint, closed session endpoint map과 runtime schema
- `PRESIGNED_MULTIPART_V1` canonical binding, server-side session lookup,
authorization/revocation
- object storage CORS, allowed method/headers, exposed receipt/checksum headers
- PUT 성공 status, receipt header, response byte length/body cap
- session expiry, 404/410 terminal 의미, list/status pagination, idempotency와
orphan cleanup
- part/full-object checksum의 정확한 알고리즘·composite 의미
- quarantine scan, promotion, status와 reject/delete lifecycle
- CDN source registry, immutable asset revision, versioned named preset의 exact
candidate 재계산과 query mismatch 거절
- image signing key overlap 배포, signer 전환, capability/client drain과
emergency revocation/forced rollout runbook
- CDN `Content-Type`, static header metadata, dimensions/decoded-byte budget,
private `no-store`, application과 분리된 CDN origin, cache key, `Vary`, CORS와 CSP
브라우저의 local file reference, native `File`/`Blob`, IndexedDB checkpoint physical
schema, OPFS path, signed URL query와 cloud object key는 backend 공유 계약이 아니다.
## 선택하지 않은 대안
- application caller가 arbitrary presigned URL을 직접 전달
- browser bundle에서 cloud signing
- 범용 JSON `HttpClient`로 binary streaming/part protocol까지 처리
- complete 응답을 scan 완료 또는 public availability로 간주
- local checkpoint만 보고 upload complete
- ETag를 무조건 MD5/SHA-256으로 해석
- private signed image URL을 query cache나 persistence에 장기 저장
- raw transform query로 CDN URL 조립
- large download의 unbounded Blob fallback
## 증적
- application ports: `src/application/ports/browser-transfer/`
- concrete adapters: `src/adapters/browser-transfer/`
- unit/fault tests: `tests/unit/`
- real browser cases: `tests/browser-capabilities/`
- 상세 설계:
`docs/architecture/presigned-transfer-and-image-cdn.md`
- Range/background 결정:
`docs/architecture/decisions/VD-14-resumable-download-and-background-transfer.md`
- composition/Image provider 결정:
`docs/architecture/decisions/VD-16-browser-transfer-composition-and-image-delivery.md`
- 운영 복구:
`docs/operations/browser-transfer-recovery.md`