Files
llm-wiki/raw/branch-notes/feature-frontend-binary-file-io-store-contract.md
T
DongHyeonka d1ae257efe docs(branch): 런타임 capability branch-note 6개 스캐폴딩
WI-...-028~033 에 대응하는 FE-OC-027~032 owner 노트.

- 각 노트의 제외 범위에 'use case·domain model·business rule' 을 명시해
  이 skeleton 이 port 와 adapter 계약까지임을 못박았다
- 근거 절에 '근거 등급 경계' 문단을 넣어 project-local default 와
  외부 근거를 구분했다 (수집은 FE-Q-011~014 가 소유)
- 구현 가이드 절은 비워 두고 사유를 남겼다 — 근거 raw 없이 채우면
  모든 cell 이 UNSUPPORTED_IMPL_DECISION 이 된다
- multi-protocol 노트는 '신규 port 0개' 를 제외 범위에 명시했다.
  port 가 늘어나면 그 자체가 회귀 신호다
2026-07-28 14:36:43 +09:00

240 lines
14 KiB
Markdown

---
title: branch / feature-frontend-binary-file-io-store-contract
source_type: branch-note
status: raw
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-028
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-028
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BINARY-STORE-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REGISTRY-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-002, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-011]
imports: [FE-GATE-027@1, FE-OC-002@1, FE-OC-013@1, FE-OC-022@1]
delegates: []
accepts_delegations: [DELEG-FE-008]
contract_packet: 1
branch: feature-frontend-binary-file-io-store-contract
parent_branch:
related_projects: [ca-skeleton-frontend, ca-skeleton]
tags: [branch, ca-skeleton-frontend, storage, binary, file-io]
created: 2026-07-28
target_merge:
status_label: in-progress
---
# branch: feature-frontend-binary-file-io-store-contract
<!-- section-id: branch-parent -->
## 부모 (필수)
- [[raw/project-notes/ca-skeleton-frontend-operational-contract]]
형제 branch (같은 부모, 이번 확장에서 함께 생성):
- [[raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract]]
- [[raw/branch-notes/feature-frontend-large-object-transfer-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`
- **완료 조건**: picker·다운로드·object URL 해제·quota·OPFS·Cache Storage fixture가 통과하고 binary I-O report가 생성된다
<!-- 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_BINARY_IO` 를 이 branch 가 소유하고 default OFF 로 유지한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BINARY-STORE-001@1` | 로컬 바이너리 backend는 IndexedDB를 default로 하고 OPFS는 대용량 순차 write에 opt-in, Cache Storage는 service worker 호스팅 response cache 전용이다 | `BlobStorePort` adapter 의 backend 선택 순서에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REGISTRY-001@1` | route, API operation, env, storage, error, query, telemetry, release token, capability를 9개 registry로 관리한다 | `FE-REG-STORAGE` 신규 4행(`UPLOAD_PART_STATE`·`TRANSFER_OBJECT_BUFFER`·`QUERY_CACHE_SNAPSHOT`·`SW_RESPONSE_CACHE`)의 `payloadClass`·`evictionOrder` 를 소비한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
| D1 | object URL 의 생성과 해제는 `adapters/file` 이 쌍으로 소유하고 presentation 에 raw URL 문자열을 넘기지 않는다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
| D2 | `BlobStorePort` 는 backend 를 호출자에게 노출하지 않고 registry 의 `backend` 값으로만 선택한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BINARY-STORE-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
<!-- section-id: declared-overrides -->
### 선언한 예외
해당 없음. inherited decision 과 다른 동작을 요구하지 않는다.
<!-- 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-027@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | picker·다운로드·quota·object URL 해제 fixture 가 실패하면 merge 를 MUST 차단 | 이 branch 가 owner 로서 fixture 와 report 를 산출 |
| `FE-OC-002@1` | [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] | `domain <- application <- presentation` 의존 방향과 application-owned output port를 MUST 지킴 | port 는 application 소유, adapter 가 구현 |
| `FE-OC-013@1` | [[raw/branch-notes/feature-frontend-storage-registry-contract]] | storage key는 namespace·version·classification을 MUST 가지며 token/secret 저장을 금지 | 바이너리 key 도 예외 없이 registry 경유 |
| `FE-OC-022@1` | [[raw/branch-notes/feature-frontend-contract-registry-governance]] | 9개 registry는 single primary owner와 compatibility impact를 MUST 기록 | `FE-REG-STORAGE` 확장의 compatibility impact 기록 |
<!-- GENERATED: project-contract-imports:end -->
<!-- GENERATED: received-delegations:start -->
### 수신한 위임
| Delegation Ref | From | Concern | Status |
|---|---|---|---|
| `DELEG-FE-008` | `feature-frontend-large-object-transfer-contract` | 전송 대상 `File`/`Blob` handle 과 object URL 수명 소유 | accepted |
<!-- GENERATED: received-delegations:end -->
<!-- GENERATED: flow:start -->
### 가져온 흐름 단계
| Stage Ref | Order | Owner | Input | Action | Output |
|---|---:|---|---|---|---|
<!-- GENERATED: flow:end -->
<!-- section-id: branch-goal -->
## 목표
`FileDialogPort``BlobStorePort` 를 정의하고, IndexedDB·OPFS·Cache Storage adapter 와 object URL 수명 계약을 고정한다. 이 계약이 없으면 컴포넌트가 `URL.createObjectURL` 을 직접 부르고 `revokeObjectURL` 을 빠뜨려 탭 수명 동안 메모리가 증가하며, quota 초과 시 correctness 값이 조용히 memory 로 fallback 된다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- `FileDialogPort` — 파일 선택(`<input type=file>` / File System Access) 과 저장 dialog
- `BlobStorePort` — IndexedDB(default) · OPFS(opt-in) · Cache Storage(SW 전용) backend
- object URL 생성·해제 쌍 관리와 누수 검출 fixture
- quota 매핑(`BLOB_STORE_QUOTA_EXCEEDED`)과 `evictionOrder` 기반 제거 순서
- `FE-REG-STORAGE` 신규 4행의 소비와 `payloadClass`·`evictionOrder` 강제
- `CAP_FE_BINARY_IO` capability 행 소유
### 제외 범위
- **use case, domain model, business rule** — 이 branch 는 port 와 adapter 계약까지만 정의한다. port 를 호출하는 use case 는 적용 프로젝트가 작성한다.
- 파일 형식별 처리 — 이미지 리사이즈·비디오 트랜스코딩·문서 파싱
- 실제 네트워크 전송 — `[[raw/branch-notes/feature-frontend-large-object-transfer-contract]]` 소유
- storage physical key·namespace·classification 정의 — `[[raw/branch-notes/feature-frontend-storage-registry-contract]]` 소유
- service worker 등록·수명주기 — `[[raw/branch-notes/feature-frontend-background-execution-worker-contract]]` 소유 (Cache Storage 는 그 위에 얹힌다)
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| `[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]]` §5.1·§6.2 | port 12개 분해와 `FE-REG-STORAGE` 확장 |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §4.4·§5.5·§5.11 | port ownership, storage registry, capability registry |
**근거 등급 경계**: `FE-D027`(backend 선택 순서)의 `Evidence / rationale``project-local default, 외부 source claim 아님` 이다. MDN File System Access·OPFS·Cache Storage 등 외부 근거 raw 는 아직 수집되지 않았으며 그 수집 계획은 `FE-Q-011` 이 소유한다. 이 branch 의 결정을 외부 표준이 뒷받침한다고 말할 수 없다.
## TODO
- [ ] `FileDialogPort` 인터페이스 확정 — 등급: `planned`
- [ ] `BlobStorePort` 인터페이스와 backend 선택 규칙 확정 — 등급: `planned`
- [ ] object URL 수명 계약과 누수 fixture — 등급: `planned`
- [ ] quota 초과 시 `evictionOrder` 동작과 `null` 행 보호 fixture — 등급: `planned`
- [ ] OPFS 순차 write fixture — 등급: `planned`
- [ ] Cache Storage 버전 파티션 fixture — 등급: `planned`
- [ ] `FE-GATE-027` binary I-O report 산출 — 등급: `planned`
## 진행 중 메모
`FE-REG-STORAGE``UPLOAD_PART_STATE``quotaFallback: 없음`, `evictionOrder: null` 이다. 이 두 값은 §9.4 의 "correctness 에 영향을 주는 값은 storage fallback 을 임의 적용하지 않는다" 를 행 단위로 구현한 것이므로, adapter 가 이 행에 대해 memory fallback 을 하면 gate negative fixture 가 잡아야 한다.
## 결정 사항
- 2026-07-28: object URL 생성·해제를 adapter 가 쌍으로 소유 / 이유: 해제 누락이 컴포넌트 단위로 흩어지면 검출이 불가능 / 검토한 대안: 컴포넌트 훅에서 `useEffect` cleanup 으로 관리 / 근거: 근거 raw 미수집, project-local 판단 (`FE-Q-011`)
<!-- section-id: decision-evidence -->
## 결정-근거 매핑
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | object URL 생성·해제를 `adapters/file` 이 쌍으로 소유 | 항상. 컴포넌트가 URL 문자열을 직접 다뤄야 하는 요구가 생기면 재검토 | 없음 — `FE-Q-011` 수집 대상 | `project-local default` | 해제 시점을 adapter 가 알 수 없는 사용 패턴(장기 미리보기)이 있을 수 있음 |
| D2 | `BlobStorePort` 가 backend 를 노출하지 않음 | 항상. OPFS 전용 최적화가 use case 레벨에서 필요해지면 재검토 | 없음 — `FE-Q-011` 수집 대상 | `project-local default` | backend 별 성능 특성이 크게 다르면 추상화가 새는 지점이 생김 |
<!-- section-id: implementation -->
## 구현 가이드
> 근거 raw 자료(`FE-Q-011`) 수집 전까지 비워 둔다. 지금 채우면 모든 cell 이 `UNSUPPORTED_IMPL_DECISION` 이 되어, 다음 작업자가 *근거 있는 결정* 과 *임의 trade-off* 를 구분할 수 없다.
<!-- section-id: edge-failure-dependency -->
## 엣지·실패·의존
- **실패·엣지 경로**
- 사용자가 dialog 를 닫음 → `FILE_PICKER_DISMISSED`, error surface 없음, telemetry event 없음
- accept/size 제약 위반 → `FILE_REJECTED`, 위반 제약만 안내하고 파일명은 telemetry 에 남기지 않음
- private mode 등으로 storage 접근 불가 → `BLOB_STORE_UNAVAILABLE`, registry `quotaFallback` 적용, `없음` 행은 terminal
- quota 초과 → `BLOB_STORE_QUOTA_EXCEEDED`, `evictionOrder` 순 제거 후 재시도. `evictionOrder: null` 행은 제거 대상이 아니다
- OPFS 미지원 브라우저 → `CAPABILITY_UNSUPPORTED`, `disabledFallback: feature-hidden`
- **다른 계약 의존**
- [[raw/branch-notes/feature-frontend-storage-registry-contract]] 의 physical key·classification 정의에 의존 — 그 계약이 바뀌면 이 branch 의 4개 행 소비가 영향받음
- [[raw/branch-notes/feature-frontend-background-execution-worker-contract]] 의 SW 등록에 의존 — Cache Storage backend 는 SW 호스팅 전제
- [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 정규화 계약에 의존 — 신규 6개 kind 가 총함수로 매핑되어야 함
<!-- section-id: claims-to-verify -->
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| picker 취소가 error surface 를 띄우지 않는다 | 취소를 실패로 처리하는 구현이 흔함 | component test — dialog 취소 후 error role 요소 0개 | `planned` |
| quota 초과 시 `UPLOAD_PART_STATE` 가 memory 로 fallback 되지 않는다 | fallback 이 기본 동작으로 새기 쉬움 | negative fixture — quota 초과 강제 후 해당 key 의 fallback 시도가 예외로 거부되는지 | `planned` |
| object URL 이 해제된다 | 브라우저가 누수를 조용히 허용 | unit test — 생성/해제 호출 쌍 카운트 일치 + adapter 파괴 후 미해제 0 | `planned` |
| OPFS 순차 write 가 IndexedDB 보다 대용량에서 유리하다 | 측정 없이 가정한 backend 선택 근거 | 벤치마크 — 동일 크기 write 지연 비교, 결과를 `FE-D027` revisit trigger 에 연결 | `needs-confirmation` |
| Cache Storage 버전 파티션이 release 간 오염을 막는다 | SW 수명주기와 얽혀 있음 | integration test — 이전 release 파티션이 새 release 에서 조회되지 않는지 | `planned` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
미생성. `/coverage` 명령이 이 repo 에 없으므로(하네스 삭제) 수기 검토로 대체한다.
## 마주친 문제
없음.
## 묶음 (이 branch에서 파생된 자료)
### Sub-branches (세부 작업)
아직 없음.
### 오류 기록 (이 branch 작업 중 발생)
아직 없음.
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
아직 없음.
### 강의 (이 작업을 위해 학습한 강의)
아직 없음.
### job-posting tie-ins (이 작업에서 파생된 글감)
아직 없음.
## 관련 일일 노트
- 아직 없음
## 완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목: 없음
- `locally-verified` 항목: 없음
- `prod-verified` 항목: 없음
- **추출하지 않을 항목**: 현재 전 항목 `planned`