Files
llm-wiki/raw/branch-notes/feature-frontend-large-object-transfer-contract.md

35 KiB
Raw Permalink Blame History

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, imports, delegates, accepts_delegations, contract_packet, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label
title source_type status id kind project work_item inherits refines overrides depends_on imports delegates accepts_delegations contract_packet branch parent_branch related_projects governing_docs tags created target_merge status_label
branch / feature-frontend-large-object-transfer-contract branch-note raw BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-030 project-work-item ca-skeleton-frontend-operational-contract WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-030
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TRANSFER-CREDENTIAL-001@1
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-RESUMABLE-TRANSFER-001@1
WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005
WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-028
FE-GATE-029@1
FE-OC-006@1
FE-OC-019@1
FE-OC-027@1
DELEG-FE-008@1
1 feature-frontend-large-object-transfer-contract
ca-skeleton-frontend
ca-skeleton
raw/project-notes/ca-skeleton-frontend-operational-contract.md
branch
ca-skeleton-frontend
transfer
upload
streaming
2026-07-28 in-progress

branch: feature-frontend-large-object-transfer-contract

부모 (필수)

형제 branch (같은 부모, 이번 확장에서 함께 생성):

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 2
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: presign 만료·part 재시도·무결성·credential 경계 fixture가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1 신규 runtime capability 6종은 FE-REG-CAPABILITY flag로 default OFF이며 활성화는 owner·gate·runbook을 동반한다 CAP_FE_LARGE_TRANSFER 를 이 branch 가 소유하고 default OFF 로 유지한다 raw/project-notes/ca-skeleton-frontend-operational-contract
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TRANSFER-CREDENTIAL-001@1 presigned URL 획득은 shared client를 경유하고 실제 byte 전송은 session credential을 첨부하지 않는 transfer adapter가 수행한다 UploadTransferPort adapter 가 credential-less transport 를 사용하도록 강제한다 raw/project-notes/ca-skeleton-frontend-operational-contract
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-RESUMABLE-TRANSFER-001@1 재개 가능 전송은 part size·병렬도·part 재시도 상한을 registry로 고정하고 part 상태를 BlobStorePort에 보존한다 TRANSFER_PART_SIZE_BYTES·TRANSFER_MAX_PARALLEL_PARTS 를 소비하고 UPLOAD_PART_STATE 에 상태를 쓴다 raw/project-notes/ca-skeleton-frontend-operational-contract

브랜치 지역 결정

Decision ID Decision Relation Supporting Claims Status
D1 transfer adapter 는 shared client 의 auth interceptor 체인을 재사용하지 않고 별도 transport 를 갖는다 refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TRANSFER-CREDENTIAL-001@1 UNSUPPORTED_DECISION — 조사 후에도 구현 형태를 지지하는 외부 근거 없음. trade-off: 대안인 "interceptor 에 skip 플래그" 는 기본이 첨부, 예외가 미첨부 라 플래그를 빠뜨린 새 경로가 곧바로 유출이 된다. 별도 transport 는 기본이 미첨부 라 실수의 방향이 안전한 쪽이다. 비용은 timeout·retry 정책이 두 벌로 갈라지는 것 proposed
D2 presigned URL 은 로그·telemetry·에러 객체에 남기지 않는다. Referer 경로는 브라우저 기본 정책이 이미 막으므로 "presigned URL 을 페이지 URL 에 넣지 않는다" 는 금지 규칙 하나로 대체한다 local raw/official-docs/mdn-referrer-policy.md#C1, #C2 (기본값이 cross-origin 에 path·query 미전송) proposed
D3 MediaUrlPolicy 는 port 가 아니라 application/policies/ 의 순수 함수다 local UNSUPPORTED_DECISION — 조사 후에도 근거 없음. trade-off: URL 파생에 I-O 가 없어 port 로 만들면 test double 만 늘고 composition root 가 커진다. 비용은 서명된 URL 을 요구하는 CDN 을 만나는 순간 순수 함수 가정이 깨져 port 승격 리팩터가 필요해지는 것 proposed
D4 다운로드 재개는 Accept-Ranges 로 지원을 판별하고 Range + If-Range 로 수행하며, 응답이 206 이 아니면 재개가 아니라 전체 재전송으로 취급한다. 416 은 terminal local raw/official-docs/mdn-http-range-fetch-transfer.md#C1, #C2, #C3, #C4, #C5, #C6 proposed
D6 part 재시도는 원본 Blob다시 slice 해서 새 body 를 만든다. 첫 시도의 body 를 보관했다 재사용하지 않는다 local raw/official-docs/mdn-http-range-fetch-transfer.md#C10 (읽힌 스트림은 disturbed 되어 재사용 불가) proposed
D7 취소는 AbortController 로만 하고 AbortErrorREQUEST_ABORTED 로 매핑한다. 응답 수신 후 body 읽기 중 취소도 같은 kind 다 local raw/official-docs/mdn-http-range-fetch-transfer.md#C7, #C8 proposed
D8 TRANSFER_PART_SIZE_BYTES 는 자유값이 아니라 부팅 시 하한 검증 대상이다. 하한은 선택 vendor 가 정하며(FE-Q-012), 미확정 동안은 5 MiB 를 잠정 하한으로 강제한다. part size × part 수 상한이 최대 전송 크기이므로 그 값을 계약값으로 노출한다 refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-RESUMABLE-TRANSFER-001@1 raw/official-docs/aws-s3-multipart-upload-limits.md#C1(5 MiB~5 GiB), #C2(마지막 part 예외), #C3(10,000 part 상한) proposed
D10 UPLOAD_PART_STATE 유실은 오류가 아니라 정상 경로다. 유실을 감지하면 재개 불가로 표시하고 처음부터 전송한다. 이 branch 는 persist() 를 요청하지 않는다 local raw/official-docs/mdn-storage-quotas-eviction-persistence.md#C1(origin 전량 eviction), #C4·#C5(persist 는 브라우저 재량), #C7(Safari ITP 7일) proposed

deferred (이번 회차 조사 범위 밖): D11 — 스트리밍 업로드(요청 body 를 ReadableStream 으로 전달). mdn-http-range-fetch-transfer.md §적용 경계 대로 이 페이지는 duplex 옵션과 HTTP/2 요구를 다루지 않는다. 현 계약은 part 단위 Blob 전송을 전제하며, 스트리밍 업로드가 필요해지면 별도 조사가 선행되어야 한다.

선언한 예외

해당 없음.

가져온 artifact 계약

Artifact Ref Owner Producer Schema Ref

가져온 프로젝트 계약

Ref Owner 요약 Branch 적용
FE-GATE-029@1 raw/project-notes/ca-skeleton-frontend-operational-contract presign 만료·part 재시도·무결성·credential 경계 fixture 가 실패하면 merge·release 를 MUST 차단 이 branch 가 owner 로서 fixture 와 report 를 산출
FE-OC-006@1 raw/branch-notes/feature-api-client-response-envelope-contract 모든 HTTP는 shared client를 MUST 통과하고 timeout·abort·response parsing을 page에서 구현하면 안 됨 presign 획득은 shared client 경유, byte 전송은 예외로 선언
FE-OC-019@1 raw/branch-notes/feature-frontend-browser-security-boundary-contract browser bundle에 secret을 넣지 않고 untrusted HTML injection을 기본 금지 presigned URL 유출 금지 규칙의 상위 계약
FE-OC-027@1 raw/branch-notes/feature-frontend-binary-file-io-store-contract 파일 선택·다운로드·로컬 바이너리 저장은 등록된 port를 MUST 경유하고, 원시 File/Blob handle과 object URL 수명은 adapter 경계를 MUST NOT 벗어남 DELEG-FE-008 로 handle 수명을 위임

수신한 위임

없음. 이 branch 는 DELEG-FE-008 의 delegator 다.

가져온 흐름 단계

Stage Ref Order Owner Input Action Output

목표

UploadTransferPortStreamingDownloadPort 를 정의하고, presigned URL 획득과 byte 전송 사이의 credential 경계를 고정한다. 이 계약이 없으면 presigned URL 로 나가는 요청에 session 헤더가 그대로 붙어 제3자 스토리지 도메인에 인증 정보가 전달된다. 재개 가능 전송의 part 상태 소유자도 함께 고정한다.

  • 이슈:
  • PR:

범위

포함 범위

  • UploadTransferPort — presign 결과 소비, part 분할·병렬·재시도, 진행 스트림, 취소
  • StreamingDownloadPortReadableStream 소비, range/resume, 진행 스트림
  • credential-less transfer transport 와 그 경계의 negative fixture
  • 무결성 검증(체크섬·크기)과 TRANSFER_INTEGRITY_MISMATCH
  • MediaUrlPolicy — CDN base·허용 transform 기반 URL 파생 (순수 함수)
  • §9.6 전송 진행 surface state
  • CAP_FE_LARGE_TRANSFER capability 행 소유

제외 범위

  • use case, domain model, business rule — port 와 adapter 계약까지만 정의한다
  • backend 의 presign 발급 endpoint 설계와 서명 알고리즘
  • object storage vendor 선택과 그 제약(part 최소 크기·만료) — FE-Q-012
  • Image CDN vendor 선택
  • File/Blob handle 과 object URL 수명 — DELEG-FE-008raw/branch-notes/feature-frontend-binary-file-io-store-contract 에 위임
  • 업로드 대상의 도메인 검증 규칙

근거 (필수, 최소 1개+)

Source 정당화하는 결정
[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]] §5.1·§5.2·§9.3 port 분해, MediaUrlPolicy 가 port 가 아닌 이유, 전송 surface state
raw/official-docs/owasp-content-security-policy-cheat-sheet browser 경계에서 credential·URL 노출을 줄이는 상위 관점
raw/official-docs/mdn-http-range-fetch-transfer D4 재개 프로토콜 · D6 part 재시도의 body 재생성 · D7 취소의 error 매핑
raw/official-docs/aws-s3-multipart-upload-limits D8 TRANSFER_PART_SIZE_BYTES 하한 검증과 part 수 상한이 전송 크기를 결정한다는 사실
raw/official-docs/mdn-referrer-policy D2 유출 경로 3개의 우선순위 재조정 (Referer 는 브라우저 기본값이 이미 방어)
raw/official-docs/mdn-storage-quotas-eviction-persistence D10 UPLOAD_PART_STATE 의 실질 수명 상한
raw/project-notes/ca-skeleton-frontend-operational-contract §5.3·§7.8 API operation registry 와 auth integration 경계

근거 등급 경계: 2026-07-28 /branch-spec 조사로 프로토콜 사실(range 재개, body 1회 소비, abort error, referrer 기본값)과 vendor 제약의 존재(S3 part size 하한)는 공식 문서 근거를 확보했다. 그러나 다음은 여전히 미근거다. (1) FE-D029 가 정한 transport 분리 방식 자체 — 유출 방지라는 목표는 자명하나 "interceptor 재사용 금지" 라는 구현 형태를 지지하는 외부 근거는 없다(D1). (2) part size 8 MiB 와 병렬도 3 이라는 구체 값 — aws-s3-multipart-upload-limits#C1 은 5 MiB 하한만 말하고 최적값을 말하지 않으며, 애초에 vendor 가 미확정이다(FE-Q-012). (3) MediaUrlPolicy 를 port 로 만들지 않는다는 D3. 세 항목 모두 아래 표에 라벨로 표시했다.

TODO

  • UploadTransferPort·StreamingDownloadPort 인터페이스 확정 — 등급: planned
  • credential-less transport 분리와 negative fixture — 등급: planned
  • presign 만료 감지와 재획득 1회 경로 — 등급: planned
  • part 재시도 상한(≤2)과 상태 보존 — 등급: planned
  • 무결성 검증과 불일치 시 재전송 1회 — 등급: planned
  • range 기반 다운로드 재개 — 등급: planned
  • MediaUrlPolicy 순수 함수와 허용 transform 강제 — 등급: planned
  • Accept-Ranges 판별 후에만 Range 를 보내는 경로 (D4) — 등급: planned
  • 재개 응답이 206 이 아니면 전체 재전송으로 전환 (D4) — 등급: planned
  • part 재시도 시 Blob 재slice negative fixture (D6) — 등급: planned
  • TRANSFER_PART_SIZE_BYTES 하한 + part 수 상한 부팅 검증 (D8) — 등급: planned
  • part state 유실 시 "재개 불가" 표면화 (D10) — 등급: planned
  • 전송 payload 에 presigned URL 문자열 0건을 증명하는 관측 등록 (관심사 7 should-fix) — 등급: planned
  • FE-GATE-029 transfer report 산출 — 등급: planned

진행 중 메모

PRESIGN_SAMPLE_UPLOADFE-REG-API 에 등록되지만 실제 byte 전송 대상 URL 은 registry 에 등록하지 않는다. registry 는 우리 backend 의 operation 장부이고, presigned URL 은 제3자 도메인의 일회용 주소이기 때문이다. 이 비대칭이 FE-OC-006("모든 HTTP 는 shared client 경유")의 유일한 선언적 예외이며, 예외라는 사실 자체를 §범위에 남긴다.

결정 사항

  • 2026-07-28: transfer adapter 가 shared client interceptor 를 재사용하지 않음 / 이유: interceptor 체인에 auth 첨부가 있으면 제3자 URL 로 새어나감 / 검토한 대안: interceptor 에 skip 플래그 추가 / 근거: FE-D029
  • 2026-07-28: MediaUrlPolicy 를 port 로 만들지 않음 / 이유: URL 파생에 I-O 가 없어 port 로 만들면 test double 만 늘어남 / 검토한 대안: MediaUrlPort / 근거: 설계문서 §5.2

결정-근거 매핑

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 별도 transport 로 credential 분리 항상. 스토리지가 same-origin proxy 만 제공하면 FE-D029 재검토 없음 — 조사 후에도 구현 형태의 근거 없음 UNSUPPORTED_DECISION 두 transport 의 timeout·retry 정책이 갈라진다. 한쪽만 고쳐지는 drift 가 생김
D2 presigned URL 을 로그·telemetry 에 남기지 않고, Referer 는 "페이지 URL 에 넣지 않기" 로 대체 항상 mdn-referrer-policy.md#C1(기본값), #C2(cross-origin 은 origin 만) official-reference(Referer 경로) + project-local default(로그 금지) 디버깅이 어려워진다. 실패를 operation ID 만으로 추적할 수 있어야 함
D3 MediaUrlPolicy 는 순수 함수 항상. CDN 이 서명된 URL 을 요구하면 port 로 승격 재검토 없음 — 조사 후에도 근거 없음 UNSUPPORTED_DECISION 서명 필요 CDN 을 만나면 순수 함수 가정이 깨져 리팩터가 필요
D4 Accept-Ranges 판별 + Range/If-Range, 206 아니면 재개 아님 서버가 range 를 지원할 때만 재개. 미지원이면 resumeStrategy 를 끄고 처음부터 mdn-http-range-fetch-transfer.md#C1~#C6 official-reference If-Range validator 로 ETag 를 쓸지 Last-Modified 를 쓸지 미정. vendor 가 무엇을 주는지에 달림(FE-Q-012)
D6 part 재시도는 Blob 재slice 항상. 예외 없음 — 프로토콜 제약이다 mdn-http-range-fetch-transfer.md#C10 official-reference 원본 Blob 을 재시도 시점까지 살려 둬야 하므로 handle 수명이 DELEG-FE-008 과 엮임
D7 AbortController 단일 취소 경로 항상 mdn-http-range-fetch-transfer.md#C7, #C8 official-reference 이미 전송된 바이트가 서버에서 정리되는지는 vendor 책임이며 프론트가 보장하지 못함
D8 part size 하한 검증 + 최대 전송 크기 노출 항상. vendor 확정 시 하한을 그 값으로 교체 aws-s3-multipart-upload-limits.md#C1, #C2, #C3 official-reference(하한의 존재) + project-local default(8 MiB 라는 값) 8 MiB × 10,000 ≈ 80 GB 라는 상한이 제품 요구를 넘는지 미확인. 이 계산은 우리 도출이지 AWS 서술이 아님
D10 part state 유실은 정상 경로 항상. persist() 요청은 하지 않음 mdn-storage-quotas-eviction-persistence.md#C1, #C4, #C5, #C7 official-reference 대용량 전송 중 origin eviction 이 일어나면 사용자는 진행률만 보다가 재개 불가를 통보받는다. UX 문구 미설계

구현 가이드

2026-07-28 /branch-spec 조사(MDN 2건 + AWS S3 1건)로 프로토콜에 의존하는 detail 을 채웠다. vendor 값에 의존하는 detail(FE-Q-012)은 여전히 미확정이므로 값이 아니라 검증 규칙으로만 적었다. 근거가 원칙만 지지하는 cell 은 UNSUPPORTED_IMPL_DECISION + trade-off 로 표시했다(CLAUDE.md §15.5 R2).

1. credential 경계 — 두 transport 의 분리 형태

Trace: D1(별도 transport) ← 근거 없음(project decision) / D2(URL 미기록) ← mdn-referrer-policy.md#C1·#C2 / 상속 DEC-…-TRANSFER-CREDENTIAL-001@1

presign 획득은 shared client 를 통과한다(FE-OC-006). byte 전송은 shared client 를 쓰지 않는 별도 함수가 수행하며, 그 함수는 auth 를 붙이는 코드에 접근할 수 없어야 한다 — 정책이 아니라 도달 불가능성으로 강제한다. 첨부 금지를 런타임 조건문으로 구현하면 조건이 하나 빠지는 순간 유출이므로, transport 자체를 분리해 첨부 코드가 그 경로에 존재하지 않게 한다.

유출 경로는 세 개가 아니라 두 개다. #C1+#C2 대로 브라우저 기본 정책(strict-origin-when-cross-origin)이 cross-origin 요청에 path·query 를 이미 보내지 않으므로 Referer 는 방어된 상태다. 남은 실질 경로는 (a) 우리 로그·telemetry·에러 객체, (b) presigned URL 이 페이지 URL·history 에 들어가는 코드. fixture 밀도를 (a)·(b) 에 몰고, Referer 에 대해서는 (b) 금지 규칙 하나만 둔다.

UNSUPPORTED_IMPL_DECISION — 분리를 "별도 모듈 + 별도 fetch wrapper" 로 구현하는 것. 근거는 목표(유출 방지)만 지지하고 형태는 지지하지 않는다. trade-off: 대안(interceptor skip 플래그)은 실수의 방향이 유출 쪽이라 배제했다. 비용은 timeout·retry 정책이 두 벌이 되는 것이며, 이를 registry 값 공유로 완화한다.

2. 다운로드 재개 프로토콜

Trace: D4 ← mdn-http-range-fetch-transfer.md#C1~#C6 / D7(취소) ← #C7·#C8

StreamingDownloadPort 는 재개를 낙관적으로 시도하지 않는다. 순서는 고정이다.

  1. 최초 응답의 Accept-Ranges 를 기록한다. 헤더가 없거나 none 이면 resumeStrategy 를 비활성화한다(#C1)
  2. 재개 시 Range 와 함께 If-Range 를 보낸다. #C5 가 요구하는 "원본 미변경 보장" 을 이 헤더가 담당한다
  3. 응답 상태를 검사한다. 206 이면 Content-Range 로 위치를 확인하고 이어붙인다(#C2). 200 이면 재개가 아니라 전체 재전송이므로 받아 둔 앞부분을 버리고 처음부터 쓴다(#C4·#C6). 416 은 재개 위치가 리소스 밖이라는 뜻이므로 terminal 로 보고 TRANSFER_INTEGRITY_MISMATCH 로 매핑한다(#C3)

3번이 이 절의 핵심이다. 200 을 성공으로 처리하면 앞부분 + 전체가 이어붙어 길이가 늘어난 파일이 만들어지고, 무결성 검사가 없으면 그대로 저장된다.

취소는 AbortController 하나로 통일한다. #C7(fetch reject)과 #C8(body 읽기 중 reject)은 다른 시점의 같은 오류이므로 둘 다 REQUEST_ABORTED 로 매핑하고 구분하지 않는다.

UNSUPPORTED_IMPL_DECISIONIf-Range 의 validator 로 ETag 를 우선하고 없으면 Last-Modified 를 쓰는 것. #C5·#C6 는 조건부 재개의 필요성만 말하고 validator 선택을 말하지 않는다. trade-off: ETag 가 더 정밀하지만 vendor 가 무엇을 주는지 미확정(FE-Q-012)이라 둘 다 처리하는 쪽을 택했다.

3. part 분할·재시도

Trace: D6(재slice) ← mdn-http-range-fetch-transfer.md#C10 / D8(하한 검증) ← aws-s3-multipart-upload-limits.md#C1~#C3 / 상속 DEC-…-RESUMABLE-TRANSFER-001@1

재시도는 body 를 재생성한다. #C10 대로 한 번 읽힌 스트림은 disturbed 상태가 되어 누구도 다시 읽지 못하므로, 재시도 시 원본 Blob 을 같은 오프셋으로 다시 slice() 한다. 첫 시도의 body 객체를 보관했다 재사용하는 구현은 두 번째 시도에서 빈 본문을 보내고, 서버는 그것을 성공으로 받는다.

part size 는 값이 아니라 검증 규칙으로 고정한다.

규칙 근거
TRANSFER_PART_SIZE_BYTES ≥ 하한 을 부팅 시 검사하고 위반이면 boot fail #C1 — 하한 미만은 마지막 part 를 제외한 전 구간에서 거부된다
마지막 part 만 하한 예외 #C2 — "no minimum size limit on the last part"
ceil(총크기 / partSize) ≤ part 수 상한 을 전송 시작 전 검사 #C3 — 상한 초과 분할은 전송 자체가 불가능
최대 전송 크기 = partSize × part 수 상한 을 계약값으로 노출 위 두 값의 우리 도출이며 AWS 서술이 아님

vendor 미확정 동안 하한은 5 MiB, part 수 상한은 10,000 을 잠정값으로 쓴다. 이 두 값의 출처가 S3 라는 사실을 registry 주석에 남겨, vendor 확정 시 무엇을 바꿔야 하는지가 코드에서 보이게 한다.

UNSUPPORTED_IMPL_DECISION — part size 8 MiB 와 병렬도 3 이라는 값. #C1 은 5 MiB~5 GiB 라는 허용 범위만 말하고 최적값을 말하지 않는다. trade-off: 하한(5 MiB)에 붙이면 part 수가 늘어 상한(10,000)에 빨리 닿고, 크게 잡으면 재시도 1회의 손실이 커진다. 8 MiB 는 그 사이의 임의 지점이며 측정으로 대체되어야 한다.

4. part 상태의 수명

Trace: D10 ← mdn-storage-quotas-eviction-persistence.md#C1·#C4·#C5·#C7

UPLOAD_PART_STATE사라질 수 있는 값으로 다룬다. #C1 대로 브라우저 eviction 은 origin 전량 삭제라 이 행만 보호할 방법이 없고, #C4·#C5 대로 persist() 는 허가가 브라우저 재량이다. 따라서 adapter 는 재개 전 part 상태의 존재와 정합성을 검사하고, 없으면 오류가 아니라 "재개 불가" 로 표면화한 뒤 처음부터 전송한다.

Safari ITP 의 7일 규칙(#C7)은 이 계약에서 구속 조건이 아니다FE-REG-STORAGEUPLOAD_PART_STATE TTL 이 24시간이라 7일보다 먼저 만료된다. 구속하는 것은 시간이 아니라 저장 압박이며, 그것은 예고 없이 온다.

persist() 요청은 이 branch 가 하지 않는다 — 소유자는 raw/branch-notes/feature-frontend-binary-file-io-store-contract D7 이다. 두 branch 가 각자 요청하면 프롬프트가 중복된다.

5. 이 branch 가 남기지 않는 것 (R3)

엣지·실패·의존

  • 실패·엣지 경로
    • presigned URL 만료 → PRESIGN_EXPIRED, 재획득 1회 후 같은 위치에서 재개. 재획득도 실패하면 사용자 주도 retry
    • part 재시도 상한 소진 → UPLOAD_PART_FAILED, part 상태 보존 후 transfer-paused
    • 체크섬/크기 불일치 → TRANSFER_INTEGRITY_MISMATCH, 해당 part 폐기 후 재전송 1회, 재실패면 terminal
    • 다운로드 스트림 중단 → STREAM_INTERRUPTED, resumeStrategy: range 면 받은 범위부터 재개
    • 사용자 취소 → REQUEST_ABORTED 재사용, part 상태 폐기 여부를 명시
    • UPLOAD_PART_STATE 저장 실패 → BLOB_STORE_UNAVAILABLE. 이 행은 fallback 이 없으므로 전송을 재개 불가로 표시하고 조용히 memory 로 넘어가지 않음
    • 재개 요청에 200 응답 → 재개가 아니라 전체 재전송이다(mdn-http-range-fetch-transfer#C4·#C6). 받아 둔 앞부분을 버리고 처음부터 쓴다. 성공으로 처리하면 길이가 늘어난 파일이 만들어진다 (D4)
    • 재개 위치가 리소스 밖416(#C3). 재시도로 회복되지 않으므로 terminal 로 보고 TRANSFER_INTEGRITY_MISMATCH 로 매핑한다
    • Accept-Ranges 부재·none → 재개 자체가 불가(#C1). resumeStrategy 를 끄고 중단 시 처음부터 다시 받는다
    • 재시도 시 body 재사용 → disturbed 스트림이라 빈 본문이 전송된다(#C10). 원본 Blob 을 재slice 한다 (D6)
    • part state 유실 → 오류가 아니라 재개 불가로 표면화하고 처음부터 전송 (D10)
    • part size 가 vendor 하한 미만 → 마지막 part 를 제외한 전 구간이 거부된다(aws-s3-multipart-upload-limits#C1). 부팅 시 검사해 boot fail (D8)
  • 다른 계약 의존

검증해야 할 주장

Claim Why uncertain How to verify Status
transfer 요청에 session credential 이 붙지 않는다 interceptor 가 전역이면 조용히 새어나감 negative fixture — 전송 요청을 가로채 Authorization·쿠키 헤더 부재 확인 planned
presigned URL 이 telemetry·로그에 남지 않는다 에러 객체에 URL 이 딸려오기 쉬움 grep + fixture — 전송 실패 시 emit 된 event payload 에 URL 문자열 0건 planned
part 재시도가 2회를 넘지 않는다 상위 재시도와 part 재시도가 곱해질 수 있음 결정론 fake clock test — 총 시도 횟수 카운트 planned
만료 후 재획득으로 같은 위치에서 재개된다 재개 위치 계산이 vendor 별로 다름 integration test — 만료 강제 후 이어받기 지점 확인 needs-confirmation
part size 8 MiB 가 실제 환경에서 합리적이다 측정 없이 정한 초기 default 벤치마크 — 네트워크 프로파일별 처리량 비교, FE-D030 revisit trigger 에 연결 needs-confirmation
UPLOAD_PART_STATE 가 quota 초과 시 memory 로 넘어가지 않는다 fallback 이 기본 동작으로 새기 쉬움 negative fixture — quota 초과 강제 후 재개 불가로 표시되는지 planned
재개 요청에 200 이 오면 처음부터 다시 쓴다 성공 상태 코드라 통과시키기 쉽고, 통과하면 길이가 늘어난 파일이 만들어짐 negative fixture — Range 를 무시하고 200 + 전체 본문을 주는 서버 stub 에 대해 최종 파일 크기가 원본과 같은지 planned
Accept-Ranges 부재 시 재개를 시도하지 않는다 판별을 건너뛰고 낙관적으로 Range 를 보내기 쉬움 fixture — 헤더 없는 응답 후 중단·재개 시 Range 헤더가 나가지 않는지 planned
part 재시도가 빈 본문을 보내지 않는다 #C10 의 disturbed 스트림 문제는 첫 시도 성공 시 드러나지 않음 fixture — 첫 시도 실패 강제 후 두 번째 요청의 Content-Length 가 part size 와 같은지 planned
part size 하한 위반이 부팅을 막는다 검증을 넣지 않으면 런타임에 vendor 거부로만 드러남 negative fixture — 하한 미만 값으로 boot 시 실패하는지 planned
최대 전송 크기(partSize × part 수 상한)가 제품 요구를 넘는다 8 MiB × 10,000 ≈ 80 GB 는 우리 도출이며 요구를 확인하지 않았음 제품 요구 확인 후 registry 값 재계산 needs-confirmation
If-Range validator 로 무엇을 써야 하는지 vendor 가 ETag 를 주는지 Last-Modified 를 주는지 미확정 (FE-Q-012) vendor 확정 후 응답 헤더 실측 needs-confirmation
대용량 전송 중 origin eviction 이 실제로 얼마나 자주 일어나는지 #C1 은 가능성만 말하고 빈도를 말하지 않음. UX 문구 설계가 이 빈도에 달림 telemetry — UPLOAD_PART_STATE 유실로 재개 불가가 된 전송 비율 needs-confirmation

Audit & Findings

/branch-spec 2026-07-28 조사에서 발견한 상위 계약과 조사 결과의 불일치. hub 소유 항목은 정합 권고만 남긴다(hub §3.3).

Finding ID 대상 현재 서술 조사 결과 권고 처리
PART_SIZE_UNVALIDATED hub §5.4 TRANSFER_PART_SIZE_BYTES / FE-D030 part size·병렬도를 registry 로 "고정" 한다고만 적고 허용 범위를 말하지 않는다 aws-s3-multipart-upload-limits#C1: part size 는 5 MiB~5 GiB 라는 vendor 하드 제약을 받고, #C3: part 수 상한 10,000 이 최대 전송 크기를 결정한다. 하한 미만 값은 마지막 part 를 제외한 전 구간에서 거부된다 env key 정의에 하한 검증과 part 수 상한 검사를 추가할 것. 값 자체는 vendor 확정(FE-Q-012) 전까지 잠정 openFE-Q-012 선행이나, 검증 규칙은 vendor 무관하므로 먼저 넣을 수 있음
REFERRER_RISK_MISWEIGHTED 이 branch 의 D2 초안 "telemetry·로그·Referrer 어디에도" — 세 경로를 같은 무게로 나열했다 mdn-referrer-policy#C1·#C2: 기본 정책 strict-origin-when-cross-origin 이 cross-origin 요청에 path·query 를 이미 보내지 않는다. 게다가 Referer요청을 유발한 문서의 URL 이지 요청 대상 URL 이 아니다 Referer 항목을 "presigned URL 을 페이지 URL 에 넣지 않는다" 는 금지 규칙으로 좁히고, fixture 밀도를 로그·telemetry 로 옮길 것 resolved 2026-07-28 — D2 서술과 §구현 가이드 1 에 반영
NO_GROUND_TRUTH /branch-spec §2 ca-tmpl 대조 명령은 ca-tmpl registry·코드와 대조하라고 요구 ca-tmpl 은 Gradle/Java 백엔드 전용. frontend 구현 repo 미식별 전 항목 planned 유지. 승격은 FE-Q-001 이후 open

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

2026-07-28 /branch-spec §8b. agent dispatch 없이 수기 판정했다(세션 제약). governing doc = raw/project-notes/ca-skeleton-frontend-operational-contract. 기준은 hub §2.2 의 10개 universal acceptance question.

# 관심사 판정 근거
1 문제와 실패 모드가 구체적인가 covered-here §목표 — 제3자 도메인으로의 credential 전달. §엣지 12개 경로
2 상속한 결정을 실제로 적용했는가 covered-here TRANSFER-CREDENTIAL-001 → D1·D2, RESUMABLE-TRANSFER-001 → D8·D10, CAPABILITY-001CAP_FE_LARGE_TRANSFER 소유
3 project-wide default 와 limit covered-here D4(206 아니면 재개 아님), D6(재slice), D8(하한 검증 + 최대 전송 크기), D10(유실은 정상 경로)
4 대안을 검토했는가 covered-here §결정 사항 — interceptor skip 플래그 / MediaUrlPort. D1·D3 는 trade-off 를 명시
5 금지 구현 covered-here §구현 가이드 — 전송 경로에 auth 첨부 코드 도달 금지, body 재사용 금지, URL 을 로그·페이지 URL 에 넣기 금지
6 실패 경로가 error kind 로 매핑되는가 covered-here PRESIGN_EXPIRED·UPLOAD_PART_FAILED·TRANSFER_INTEGRITY_MISMATCH·STREAM_INTERRUPTED·REQUEST_ABORTED·BLOB_STORE_UNAVAILABLE
7 관측 가능한가 should-fix 진행률·재개·part 재시도 telemetry 는 hub §5.8 에 있으나, URL 미유출을 증명하는 관측(payload grep gate)이 fixture 로만 있고 registry event 로 등록되지 않았다
8 위임 경계가 명확한가 covered-here DELEG-FE-008, §구현 가이드 5 의 R3 목록
9 검증 수단이 있는가 covered-here §검증해야 할 주장 14행, FE-GATE-029 fixture + transfer report
10 말할 수 있는 범위 covered-here 전 항목 plannedNO_GROUND_TRUTH

판정: Covered (missing 0) · Should-fix 1건(관심사 7). Blocking 아님.

마주친 문제

없음.

묶음 (이 branch에서 파생된 자료)

Sub-branches (세부 작업)

아직 없음.

오류 기록 (이 branch 작업 중 발생)

아직 없음.

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

아직 없음.

강의 (이 작업을 위해 학습한 강의)

아직 없음.

job-posting tie-ins (이 작업에서 파생된 글감)

아직 없음.

관련 일일 노트

  • 아직 없음

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: 없음
    • locally-verified 항목: 없음
    • prod-verified 항목: 없음
  • 추출하지 않을 항목: 현재 전 항목 planned