- REGISTRY-001 Summary 문자열 4곳 (additive, revision 은 1 유지). registry-governance 는 본문 서술 12곳의 '8개'도 함께 9개로 갱신 - OFFLINE-CACHE-001 pin @1->@2 와 Summary·적용점 (behavior-change) - DELEG-FE-007~011 delegate 5곳 접수 완료 — 미접수 위임 0건 - runtime-schema-validation 이 FLOW-FE-EVENT-003/004 소유를 명시 - env-runtime-config 가 FE-REG-CAPABILITY registry 와 FE-GATE-033 gate owner 를 취득하고 claim 2건 추가 - 신규 6개 노트의 delegation pin 을 기존 @1 형식으로 정규화
248 lines
14 KiB
Markdown
248 lines
14 KiB
Markdown
---
|
|
title: branch / feature-frontend-large-object-transfer-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-030
|
|
kind: project-work-item
|
|
project: ca-skeleton-frontend-operational-contract
|
|
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-030
|
|
inherits: [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]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-028]
|
|
imports: [FE-GATE-029@1, FE-OC-006@1, FE-OC-019@1, FE-OC-027@1]
|
|
delegates: [DELEG-FE-008@1]
|
|
accepts_delegations: []
|
|
contract_packet: 1
|
|
branch: feature-frontend-large-object-transfer-contract
|
|
parent_branch:
|
|
related_projects: [ca-skeleton-frontend, ca-skeleton]
|
|
tags: [branch, ca-skeleton-frontend, transfer, upload, streaming]
|
|
created: 2026-07-28
|
|
target_merge:
|
|
status_label: in-progress
|
|
---
|
|
|
|
# branch: feature-frontend-large-object-transfer-contract
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- [[raw/project-notes/ca-skeleton-frontend-operational-contract]]
|
|
|
|
형제 branch (같은 부모, 이번 확장에서 함께 생성):
|
|
|
|
- [[raw/branch-notes/feature-frontend-binary-file-io-store-contract]]
|
|
- [[raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract]]
|
|
- [[raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract]]
|
|
- [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]]
|
|
- [[raw/branch-notes/feature-frontend-background-execution-worker-contract]]
|
|
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `2`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: presign 만료·part 재시도·무결성·credential 경계 fixture가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| 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]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
| 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` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
|
|
| D2 | presigned URL 은 telemetry·로그·`Referrer` 어디에도 남기지 않는다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
|
|
| D3 | `MediaUrlPolicy` 는 port 가 아니라 `application/policies/` 의 순수 함수다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
해당 없음.
|
|
|
|
<!-- GENERATED: artifact-imports:start -->
|
|
### 가져온 artifact 계약
|
|
|
|
| Artifact Ref | Owner | Producer | Schema Ref |
|
|
|---|---|---|---|
|
|
<!-- GENERATED: artifact-imports:end -->
|
|
|
|
<!-- GENERATED: project-contract-imports:start -->
|
|
## 가져온 프로젝트 계약
|
|
|
|
| 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 수명을 위임 |
|
|
<!-- GENERATED: project-contract-imports:end -->
|
|
|
|
<!-- GENERATED: received-delegations:start -->
|
|
### 수신한 위임
|
|
|
|
없음. 이 branch 는 `DELEG-FE-008` 의 delegator 다.
|
|
|
|
<!-- GENERATED: received-delegations:end -->
|
|
|
|
<!-- GENERATED: flow:start -->
|
|
### 가져온 흐름 단계
|
|
|
|
| Stage Ref | Order | Owner | Input | Action | Output |
|
|
|---|---:|---|---|---|---|
|
|
<!-- GENERATED: flow:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
`UploadTransferPort` 와 `StreamingDownloadPort` 를 정의하고, presigned URL 획득과 byte 전송 사이의 credential 경계를 고정한다. 이 계약이 없으면 presigned URL 로 나가는 요청에 session 헤더가 그대로 붙어 제3자 스토리지 도메인에 인증 정보가 전달된다. 재개 가능 전송의 part 상태 소유자도 함께 고정한다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- `UploadTransferPort` — presign 결과 소비, part 분할·병렬·재시도, 진행 스트림, 취소
|
|
- `StreamingDownloadPort` — `ReadableStream` 소비, 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-008` 로 [[raw/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/project-notes/ca-skeleton-frontend-operational-contract]] §5.3·§7.8 | API operation registry 와 auth integration 경계 |
|
|
|
|
**근거 등급 경계**: `FE-D029`(credential 경계)의 rationale 은 `credential 유출 방지 invariant, project decision` 이고 `FE-D030`(part 정책)은 `project-local default, 외부 source claim 아님` 이다. presigned URL·multipart 의 vendor 별 제약을 다룬 raw 자료는 없으며 `FE-Q-012` 가 수집을 소유한다. part size 8 MiB·병렬도 3 은 측정값이 아니라 초기 default 다.
|
|
|
|
## TODO
|
|
|
|
- [ ] `UploadTransferPort`·`StreamingDownloadPort` 인터페이스 확정 — 등급: `planned`
|
|
- [ ] credential-less transport 분리와 negative fixture — 등급: `planned`
|
|
- [ ] presign 만료 감지와 재획득 1회 경로 — 등급: `planned`
|
|
- [ ] part 재시도 상한(≤2)과 상태 보존 — 등급: `planned`
|
|
- [ ] 무결성 검증과 불일치 시 재전송 1회 — 등급: `planned`
|
|
- [ ] range 기반 다운로드 재개 — 등급: `planned`
|
|
- [ ] `MediaUrlPolicy` 순수 함수와 허용 transform 강제 — 등급: `planned`
|
|
- [ ] `FE-GATE-029` transfer report 산출 — 등급: `planned`
|
|
|
|
## 진행 중 메모
|
|
|
|
`PRESIGN_SAMPLE_UPLOAD` 는 `FE-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
|
|
|
|
<!-- section-id: decision-evidence -->
|
|
## 결정-근거 매핑
|
|
|
|
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|---|
|
|
| D1 | 별도 transport 로 credential 분리 | 항상. 스토리지가 same-origin proxy 만 제공하면 `FE-D029` 재검토 | 없음 — `FE-Q-011`, `FE-Q-012` | `project decision` | 두 transport 의 timeout·retry 정책이 갈라질 수 있음 |
|
|
| D2 | presigned URL 을 어디에도 남기지 않음 | 항상 | 없음 — `FE-Q-011` | `project-local default` | 디버깅이 어려워짐. 실패 시 operation ID 만으로 추적 가능해야 함 |
|
|
| D3 | `MediaUrlPolicy` 는 순수 함수 | 항상. CDN 이 서명된 URL 을 요구하면 port 로 승격 재검토 | 없음 — `FE-Q-011` | `project-local default` | 서명 필요 CDN 을 만나면 설계가 바뀜 |
|
|
|
|
<!-- section-id: implementation -->
|
|
## 구현 가이드
|
|
|
|
> 근거 raw 자료(`FE-Q-011`, `FE-Q-012`) 수집 전까지 비워 둔다. 특히 part size·병렬도·만료 처리는 storage vendor 제약에 직접 의존하므로, vendor 확정 전에 쓰면 전부 `UNSUPPORTED_IMPL_DECISION` 이 된다.
|
|
|
|
<!-- section-id: edge-failure-dependency -->
|
|
## 엣지·실패·의존
|
|
|
|
- **실패·엣지 경로**
|
|
- 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 로 넘어가지 않음
|
|
- **다른 계약 의존**
|
|
- [[raw/branch-notes/feature-frontend-binary-file-io-store-contract]] 의 `BlobStorePort`·handle 수명에 의존 (`DELEG-FE-008`)
|
|
- [[raw/branch-notes/feature-api-client-response-envelope-contract]] 의 shared client 에 의존 — presign 획득 경로
|
|
- [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] 의 header·CSP 정책에 의존 — `Referrer-Policy` 로 URL 유출 차단
|
|
|
|
<!-- section-id: claims-to-verify -->
|
|
## 검증해야 할 주장
|
|
|
|
| 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` |
|
|
|
|
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
|
|
|
|
미생성.
|
|
|
|
## 마주친 문제
|
|
|
|
없음.
|
|
|
|
## 묶음 (이 branch에서 파생된 자료)
|
|
|
|
### Sub-branches (세부 작업)
|
|
|
|
아직 없음.
|
|
|
|
### 오류 기록 (이 branch 작업 중 발생)
|
|
|
|
아직 없음.
|
|
|
|
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
|
|
|
아직 없음.
|
|
|
|
### 강의 (이 작업을 위해 학습한 강의)
|
|
|
|
아직 없음.
|
|
|
|
### job-posting tie-ins (이 작업에서 파생된 글감)
|
|
|
|
아직 없음.
|
|
|
|
## 관련 일일 노트
|
|
|
|
- 아직 없음
|
|
|
|
## 완료 후 정리
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목: 없음
|
|
- `locally-verified` 항목: 없음
|
|
- `prod-verified` 항목: 없음
|
|
- **추출하지 않을 항목**: 현재 전 항목 `planned`
|