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 가 늘어나면 그 자체가 회귀 신호다
240 lines
14 KiB
Markdown
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`
|