diff --git a/raw/branch-notes/feature-frontend-background-execution-worker-contract.md b/raw/branch-notes/feature-frontend-background-execution-worker-contract.md index b47f4b0..ca5e6dc 100644 --- a/raw/branch-notes/feature-frontend-background-execution-worker-contract.md +++ b/raw/branch-notes/feature-frontend-background-execution-worker-contract.md @@ -17,6 +17,7 @@ contract_packet: 1 branch: feature-frontend-background-execution-worker-contract parent_branch: related_projects: [ca-skeleton-frontend, ca-skeleton] +governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md] tags: [branch, ca-skeleton-frontend, worker, service-worker, background-sync] created: 2026-07-28 target_merge: diff --git a/raw/branch-notes/feature-frontend-binary-file-io-store-contract.md b/raw/branch-notes/feature-frontend-binary-file-io-store-contract.md index 02548ec..9c362e2 100644 --- a/raw/branch-notes/feature-frontend-binary-file-io-store-contract.md +++ b/raw/branch-notes/feature-frontend-binary-file-io-store-contract.md @@ -17,6 +17,7 @@ contract_packet: 1 branch: feature-frontend-binary-file-io-store-contract parent_branch: related_projects: [ca-skeleton-frontend, ca-skeleton] +governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md] tags: [branch, ca-skeleton-frontend, storage, binary, file-io] created: 2026-07-28 target_merge: @@ -59,8 +60,15 @@ status_label: in-progress | 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` | +| D1 | object URL 의 생성과 해제는 `adapters/file` 이 쌍으로 소유하고 presentation 에 raw URL 문자열을 넘기지 않는다 | `local` | `raw/official-docs/mdn-object-url-cache-storage.md#C1`, `#C2` | `proposed` | +| D2 | `BlobStorePort` 는 backend 를 호출자에게 노출하지 않고 registry 의 `backend` 값으로만 선택한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BINARY-STORE-001@1` | **`UNSUPPORTED_DECISION`** — 조사 후에도 근거 없음. trade-off: backend 를 노출하면 호출부가 IndexedDB/OPFS 를 직접 분기해 `FE-OC-002` 의 port 경계가 새므로 은닉을 택했다. 비용은 backend 별 최적화 불가 | `proposed` | +| D3 | `FileDialogPort` 의 **기준선은 `` + ``** 이고, File System Access picker 는 feature detection 후 얹는 progressive enhancement 다 | `local` | `raw/official-docs/mdn-file-system-access-opfs.md#C1`, `#C2`, `#C4` | `proposed` | +| D4 | picker 취소와 "user agent 가 위험하다고 판단한 파일"은 같은 `AbortError` 로 오므로 **구분하지 않고** 둘 다 `FILE_PICKER_DISMISSED` 로 정규화한다 | `local` | `raw/official-docs/mdn-file-system-access-opfs.md#C3` | `proposed` | +| D5 | OPFS 는 **비동기 API 를 기본**으로 쓰고, 동기 access handle 이 필요한 경우에만 `WorkerTaskPort` 를 경유한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BINARY-STORE-001@1` | `raw/official-docs/mdn-file-system-access-opfs.md#C5`, `#C6` | `proposed` | +| D6 | `FE-REG-STORAGE.evictionOrder` 는 **애플리케이션 주도 정리** 순서이며 브라우저 eviction 에는 적용되지 않는다 | `local` | `raw/official-docs/mdn-storage-quotas-eviction-persistence.md#C1`, `#C3` | `proposed` | +| D7 | correctness 값을 쓰는 capability 는 boot 시 `navigator.storage.persist()` 를 1회 요청하고, **미허가 상태를 실패로 취급하지 않되 재개 보장 없음을 표면화**한다 | `local` | `raw/official-docs/mdn-storage-quotas-eviction-persistence.md#C4`, `#C5`, `#C7` | `proposed` | + +`D8`(IndexedDB 스키마·버전 관리와 migration 절차)은 이번 회차 자동조사 상한(6개)을 넘어 **deferred** 다. 다음 `/branch-spec` 회차 또는 수동 조사 대상이며, 그 전까지 §구현 가이드에 IndexedDB 버전 전략을 적지 않는다. ### 선언한 예외 @@ -132,10 +140,18 @@ status_label: in-progress | Source | 정당화하는 결정 | |---|---| +| [[raw/official-docs/mdn-file-system-access-opfs]] | D3 picker 기준선 선택 · D4 취소 매핑 · D5 OPFS worker 제약 | +| [[raw/official-docs/mdn-storage-quotas-eviction-persistence]] | D6 `evictionOrder` 적용 범위 · D7 correctness 값의 persist 요청 | +| [[raw/official-docs/mdn-object-url-cache-storage]] | D1 object URL 수명 소유 · Cache Storage 실행 컨텍스트 전제 | | `[[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 의 결정을 외부 표준이 뒷받침한다고 말할 수 없다. +**근거 등급 경계 (2026-07-28 조사 후 갱신)**: 2026-07-28 `/branch-spec` 조사로 MDN 근거 3건을 수집해 D1·D3~D7 은 `official-reference` 근거를 갖게 됐다. 남은 경계는 두 가지다. + +- **D2 는 여전히 project-local** — port 가 backend 를 노출하지 않는다는 추상화 선택은 외부 문서가 다루는 주제가 아니다. +- **`FE-D027` 의 backend 우선순위(IndexedDB default / OPFS opt-in)는 성능 근거가 없다** — `mdn-file-system-access-opfs#C6` 이 말하는 OPFS 의 속도 우위는 **File System Access API 대비**이지 IndexedDB 대비가 아니다. IndexedDB↔OPFS 비교는 자체 벤치마크가 필요하며 `## 검증해야 할 주장` 에 남긴다. + +`ca-tmpl` ground truth 는 이 branch 에 **적용되지 않는다**(`NO_GROUND_TRUTH`) — §Audit & Findings 참조. ## TODO @@ -160,13 +176,80 @@ status_label: in-progress | 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 별 성능 특성이 크게 다르면 추상화가 새는 지점이 생김 | +| D1 | object URL 생성·해제를 `adapters/file` 이 쌍으로 소유 | 항상. 컴포넌트가 URL 문자열을 장기 보유해야 하는 요구(장기 미리보기)가 생기면 handle 기반 대여 API 로 재검토 | `mdn-object-url-cache-storage.md#C1`(해제는 명시 호출 필요), `#C2`(SW 미제공 사유가 memory leak) | `official-reference` + project-decision(소유 주체는 MDN 이 말하지 않음) | 해제 시점을 adapter 가 알 수 없는 사용 패턴. 대여 만료 타이머가 필요할 수 있음 | +| D2 | `BlobStorePort` 가 backend 를 노출하지 않음 | 항상. OPFS 전용 최적화가 use case 레벨에서 필요해지면 재검토 | **`UNSUPPORTED_DECISION`** — 조사 후에도 외부 근거 없음 | `project-local default` (라벨됨) | backend 별 성능 특성이 크게 다르면 추상화가 새는 지점이 생김 | +| D3 | `` + `` 를 기준선, File System Access picker 를 progressive enhancement | 항상. 대상 브라우저 매트릭스가 picker 전량 지원으로 확정되면(`FE-Q-007`) 기준선을 picker 로 올릴 수 있음 | `mdn-file-system-access-opfs.md#C1`(not Baseline), `#C2`(transient activation), `#C4`(secure context) | `official-reference` | 두 경로의 UX 가 달라진다 — picker 는 저장 위치 선택, fallback 은 브라우저 다운로드 폴더 고정 | +| D4 | 취소와 "위험 파일 거부"를 구분하지 않고 `FILE_PICKER_DISMISSED` 로 정규화 | 항상. 브라우저가 두 사유를 구분 가능한 신호로 분리하면 재검토 | `mdn-file-system-access-opfs.md#C3`(둘 다 같은 `AbortError`) | `official-reference` | 거부 사유를 사용자에게 설명할 수 없다. "선택된 파일이 없습니다" 수준의 중립 문구만 가능 | +| D5 | OPFS 는 비동기 API 기본, 동기 handle 이 필요할 때만 `WorkerTaskPort` 경유 | 대용량 순차 write 로 메인 스레드 블로킹이 측정될 때만 동기 경로. 그 전에는 비동기 | `mdn-file-system-access-opfs.md#C5`(동기 API 는 worker 전용), `#C6`(속도 우위의 비교 대상은 File System Access API) | `official-reference` | worker 경유는 `CAP_FE_BACKGROUND_EXEC` 활성을 전제한다 — 두 capability 가 얽힌다 | +| D6 | `evictionOrder` 는 애플리케이션 주도 정리 순서로만 유효 | 항상. 브라우저가 per-key eviction 힌트 API 를 제공하면 재검토 | `mdn-storage-quotas-eviction-persistence.md#C1`(origin 전량 삭제), `#C3`(LRU origin 단위) | `official-reference` | hub §5.5 의 필드 설명이 이 경계를 담고 있지 않다 — §Audit & Findings `EVICTION_SCOPE_DRIFT` | +| D7 | correctness 값 사용 시 `persist()` 1회 요청 + 미허가를 표면화 | correctness 값(`UPLOAD_PART_STATE` 등)을 쓰는 capability 가 활성일 때. 순수 캐시만 쓰면 요청하지 않음 | `mdn-storage-quotas-eviction-persistence.md#C4`(persist 는 LRU 제외), `#C5`(허가는 브라우저 재량), `#C7`(Safari 7일 규칙) | `official-reference` | persist 미허가 + Safari ITP 조합이면 재개 가능 전송이 7일 만에 무효가 된다. 제품이 이를 수용 가능한지 미확인 | ## 구현 가이드 -> 근거 raw 자료(`FE-Q-011`) 수집 전까지 비워 둔다. 지금 채우면 모든 cell 이 `UNSUPPORTED_IMPL_DECISION` 이 되어, 다음 작업자가 *근거 있는 결정* 과 *임의 trade-off* 를 구분할 수 없다. +> 2026-07-28 `/branch-spec` 조사(MDN 3건)로 in-scope detail 을 채웠다. 근거가 원칙만 지지하고 detail 은 지지하지 않는 cell 은 `UNSUPPORTED_IMPL_DECISION` + trade-off 한 줄로 표시했다(CLAUDE.md §15.5 R2). 다른 branch 결정 영역은 남기지 않았다(R3). +> **구현 repository 가 아직 없으므로 아래 경로·명명은 전부 `planned`** 다. `actually-implemented` 로 승격하려면 repo·commit·path 가 필요하다(hub §17.2). + +### 1. `FileDialogPort` — 2경로 구조와 feature detection + +> **Trace**: D3(기준선 선택) ← `mdn-file-system-access-opfs.md#C1`·`#C2`·`#C4` / D4(취소 정규화) ← 같은 문서 `#C3` +> +> - **UNSUPPORTED_IMPL_DECISION**: port 메서드 이름(`pickFiles`/`saveFile`)과 descriptor 필드명. MDN 은 API 표면만 말하고 우리 port 의 명명을 지지하지 않는다. trade-off: 브라우저 API 명(`showOpenFilePicker`)을 그대로 쓰면 fallback 경로에서 이름이 거짓이 되므로 중립 동사를 택했다. + +| 항목 | planned 명세 | 근거 | +|---|---|---| +| 경로 | `src/adapters/file/file-dialog.adapter.js` | — (`UNSUPPORTED_IMPL_DECISION`, hub §4.6 blueprint 관례를 따름) | +| detection | `typeof window.showOpenFilePicker === 'function'` **그리고** `window.isSecureContext` 가 모두 참일 때만 picker 경로 | `#C1`, `#C4` | +| 열기 — enhanced | `showOpenFilePicker(...)` → `FileSystemFileHandle[]` | `#C1` | +| 열기 — 기준선 | 숨긴 `` 을 클릭 이벤트 핸들러 안에서 트리거 → `FileList` | `#C2`(제스처 필요) | +| 저장 — enhanced | `showSaveFilePicker(...)` → `FileSystemFileHandle` → `createWritable()` | `#C1` | +| 저장 — 기준선 | object URL + `` 클릭. **덮어쓰기 불가**이며 저장 위치를 사용자가 고를 수 없음을 UI 문구에서 약속하지 않음 | `#C1` (fallback 한계) | +| 호출 컨텍스트 | 두 경로 모두 **사용자 제스처 핸들러 안에서 동기적으로** 호출한다. `await` 이후 호출하면 activation 이 소모돼 실패한다 | `#C2` | +| 취소 | `AbortError` → `FILE_PICKER_DISMISSED`. **사유를 구분하지 않는다** | `#C3`, D4 | +| 제약 위반 | accept/size 검사는 adapter 가 수행 → `FILE_REJECTED`. 파일명은 telemetry 로 보내지 않음 | hub §5.8 forbiddenAttributes | + +### 2. object URL 수명 — 대여(lease) 모델 + +> **Trace**: D1 ← `mdn-object-url-cache-storage.md#C1`(해제는 명시 호출)·`#C2`(SW 미제공 사유 = memory leak) +> +> - **UNSUPPORTED_IMPL_DECISION**: "대여 handle" 이라는 형태 자체. MDN 은 해제 필요성만 말하고 소유·반납 모델을 말하지 않는다. trade-off: 컴포넌트가 URL 문자열을 들고 있으면 해제 시점을 adapter 가 알 수 없어, 반납 가능한 handle 로 감쌌다. + +| 규칙 | planned 동작 | +|---|---| +| 발급 | adapter 가 `createObjectURL` 호출 후 `{ url, release() }` 형태의 대여 handle 을 반환. raw 문자열 단독 반환 금지 | +| 반납 | `release()` 가 `revokeObjectURL` 을 1회만 호출(멱등). 이중 호출은 no-op | +| adapter 파괴 | 미반납 대여를 전부 revoke 하고, 그 수를 `WORKER`/`FILE` 계열이 아닌 **gate fixture 용 카운터**로 노출 | +| service worker | object URL 을 만들지 않는다 — 플랫폼이 제공하지 않는다 (`#C2`) | +| 누수 판정 | adapter 파괴 시점의 미반납 대여 수 > 0 이면 `FE-GATE-027` 실패 | + +### 3. `BlobStorePort` — backend 선택과 quota 처리 + +> **Trace**: D2(backend 비노출) ← 근거 없음(project-local) / D5(OPFS 비동기 기본) ← `mdn-file-system-access-opfs.md#C5`·`#C6` / D6(evictionOrder 범위) ← `mdn-storage-quotas-eviction-persistence.md#C1`·`#C3` / D7(persist) ← 같은 문서 `#C4`·`#C5`·`#C7` +> +> - **UNSUPPORTED_IMPL_DECISION**: IndexedDB 를 default 로, OPFS 를 opt-in 으로 두는 **우선순위**. `#C6` 의 OPFS 속도 우위는 File System Access API 대비이지 IndexedDB 대비가 아니다. trade-off: IndexedDB 가 지원 범위가 넓고 구조화 값과 바이너리를 한 backend 로 다룰 수 있어 기준선으로 두었다. 벤치마크 전까지 이 순서는 측정 근거가 없다. + +| 항목 | planned 명세 | 근거 | +|---|---|---| +| backend 선택 | 호출자는 registry `backend` 값만 지정. adapter 가 구현체를 고른다 | D2 | +| OPFS 접근 | 비동기 File System API 기본. `createSyncAccessHandle()` 은 worker 안에서만 가능하므로 `WorkerTaskPort` 경유 | `#C5` | +| quota 초과 | `evictionOrder` 오름차순으로 **애플리케이션이** 정리 후 1회 재시도. `evictionOrder: null` 행은 정리 대상에서 제외 | D6 | +| 브라우저 eviction | 막을 수 없다. origin 전량 삭제이므로 부분 복구 로직을 두지 않는다 | `#C1`, `#C3` | +| persist 요청 | `CAP_FE_BINARY_IO` 활성 boot 시 `navigator.storage.persist()` 1회. 결과를 capability 상태에 기록 | `#C4`, `#C5` | +| persist 미허가 | 실패가 아니다. 기능은 그대로 동작하되 correctness 값 사용 화면에 "재개가 보장되지 않음" 을 표시 | `#C5`, D7 | +| Safari 경고 | ITP 활성 + 7일 무상호작용이면 script 생성 데이터가 삭제된다. 재개 가능 전송의 상한을 이 값 이하로 잡는다 | `#C7` | + +### 4. Cache Storage backend + +> **Trace**: `mdn-object-url-cache-storage.md#C3`(SW 전용 아님)·`#C4`(버전 캐시명)·`#C5`(`keys()`/`delete()`) +> +> - **UNSUPPORTED_IMPL_DECISION**: 캐시명 형식 `-`. MDN 예제는 `` `myapp-${cacheVersion}` `` 패턴만 보이고 우리 토큰 구성을 지지하지 않는다. trade-off: `FE-REG-RELEASE.releaseId` 를 재사용하면 rollback 시 파티션이 자동으로 갈라진다. + +| 항목 | planned 명세 | 근거 | +|---|---|---| +| 접근 | `Window.caches` 로도 접근 가능하다 — **service worker 는 기술적 필수 조건이 아니다** | `#C3` | +| 정책 | 그럼에도 이 skeleton 은 Cache Storage 를 SW 호스팅 response cache 로 **한정한다**. 이는 플랫폼 제약이 아니라 `FE-D027` 의 정책 선택이다 | §Audit & Findings | +| 파티션 | 캐시명에 release 토큰 포함. 활성 release 외의 캐시는 `keys()` 열거 후 `delete()` | `#C4`, `#C5` | +| 정리 시점 | SW activate 단계. 이 branch 는 파티션 규칙만 소유하고 실행 시점은 `feature-frontend-background-execution-worker-contract` 소유 | R3 경계 | ## 엣지·실패·의존 @@ -177,10 +260,12 @@ status_label: in-progress - 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 가 총함수로 매핑되어야 함 +- **다른 계약 의존** (대상 브랜치 + 그 Decision ID) + - [[raw/branch-notes/feature-frontend-storage-registry-contract]] `D1`(versioned namespace)·`D2`(classification 필수)·`D5`(`quotaFallback` 을 registry field 로 관리)·`D6`(token·secret·PII 저장 거부) — 본 branch 는 `FE-REG-STORAGE` 신규 4행을 *소비*만 하고 key 형식·classification 을 정의하지 않는다. `D5` 가 바뀌면 본 branch 의 quota 처리(§구현 가이드 3)가 함께 바뀐다. + - [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] `D1`(총함수 정규화)·`D2`(safe field 만 보존)·`D3`(`FE-REG-ERROR` single-owner)·`D6`(recovery action closed vocabulary) — 본 branch 가 만드는 4개 kind(`FILE_PICKER_DISMISSED`·`FILE_REJECTED`·`BLOB_STORE_UNAVAILABLE`·`BLOB_STORE_QUOTA_EXCEEDED`)는 `D1` 의 총함수에 매핑되어야 하고, `D6` 의 닫힌 action 어휘 밖의 action 을 만들 수 없다. + - [[raw/branch-notes/feature-frontend-background-execution-worker-contract]] `D3`(worker/SW 엔트리의 상위 layer import 금지) — **D5 의 OPFS 동기 경로**가 `WorkerTaskPort` 를 요구하므로, worker 엔트리 규칙이 본 branch 의 OPFS 구현 형태를 제약한다. Cache Storage backend 의 SW 호스팅은 `D1`(자동 `skipWaiting` 금지)·`D2`(`serviceWorkerVersion`)와 파티션 시점을 공유한다. + - [[raw/branch-notes/feature-frontend-env-runtime-config-contract]] `D4`(React mount 전 runtime config fetch·검증) — **D7 의 `persist()` 요청 시점**이 boot 순서(hub §4.5 6단계 capability 해석) 안에 있어야 한다. `D4` 의 mount-전 게이트가 바뀌면 persist 요청 위치도 바뀐다. + - [[raw/branch-notes/feature-frontend-large-object-transfer-contract]] `D1`(별도 credential-less transport) — `DELEG-FE-008@1` 로 본 branch 가 `File`/`Blob` handle 과 object URL 수명을 **수신**했다. 그 branch 의 전송 descriptor 형태가 바뀌면 본 branch 의 대여 handle 반환 형태가 영향받는다. ## 검증해야 할 주장 @@ -189,13 +274,43 @@ status_label: in-progress |---|---|---|---| | 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` | +| object URL 이 해제된다 | 브라우저가 누수를 조용히 허용 | unit test — 대여/반납 카운트 일치 + adapter 파괴 후 미반납 0 | `planned` | +| **OPFS 순차 write 가 IndexedDB 보다 대용량에서 유리하다** | `mdn-file-system-access-opfs#C6` 의 속도 우위는 **File System Access API 대비**이지 IndexedDB 대비가 아니다. `FE-D027` 의 backend 우선순위는 이 점에서 측정 근거가 없다 | 벤치마크 — 동일 크기 순차 write 지연을 IndexedDB·OPFS 비동기·OPFS 동기(worker) 3경로로 비교. 결과를 `FE-D027` revisit trigger 에 연결 | `needs-confirmation` | | Cache Storage 버전 파티션이 release 간 오염을 막는다 | SW 수명주기와 얽혀 있음 | integration test — 이전 release 파티션이 새 release 에서 조회되지 않는지 | `planned` | +| **`navigator.storage.persist()` 가 대상 브라우저에서 실제로 허가된다** | `#C5` 가 "browser may or may not honor" 로만 말한다. 허가 조건은 브라우저별 비공개 휴리스틱 | 대상 브라우저 매트릭스(`FE-Q-007`)에서 `persist()` 반환값 실측 + `persisted()` 재확인 | `needs-confirmation` | +| **재개 가능 전송이 Safari ITP 7일 규칙을 견딘다** | `#C7` 에 따르면 사용자 상호작용 없이 7일 지나면 script 생성 데이터가 삭제된다. persist 미허가 시 방어 수단이 없다 | Safari + ITP 환경에서 7일 경과 시나리오 재현 또는 제품이 7일 상한을 수용하는지 결정 | `needs-confirmation` | +| **File System Access picker 가 대상 브라우저에서 동작한다** | `#C1` 이 "not Baseline… does not work in some of the most widely-used browsers" 로만 말하고 구체 목록을 주지 않는다 | `FE-Q-007` 브라우저 매트릭스 확정 후 `FE-NFR-C02` 크로스브라우저 fixture 에서 두 경로 모두 실행 | `needs-confirmation` | +| 사용자 제스처 핸들러에서 `await` 이후 picker 를 호출하면 실패한다 | `#C2` 는 transient activation 요구만 말하고 소모 시점을 명시하지 않는다 | fixture — `await` 뒤 picker 호출이 거부되는지 확인. 거부되면 adapter 의 동기 호출 규칙이 필수임이 확정됨 | `needs-confirmation` | + +## Audit & Findings + +> `/branch-spec` 2026-07-28 조사에서 발견한 **상위 계약의 사실 오류**. 이 branch 가 자동 수정하지 않고 정합 권고만 남긴다 — `FE-D027` 과 `FE-REG-STORAGE` 는 hub 소유이며 변경은 hub §3.3 프로토콜을 따라야 한다. + +| Finding ID | 대상 | 현재 서술 | 조사 결과 | 권고 | +|---|---|---|---|---| +| `EVICTION_SCOPE_DRIFT` | hub §5.5 `FE-REG-STORAGE.evictionOrder` | "quota 압박 시 제거 순서(정수, 낮을수록 먼저)" — 주체가 명시되지 않아 브라우저 eviction 에도 적용되는 것처럼 읽힌다 | `mdn-storage-quotas-eviction-persistence#C1`: "When an origin's data is evicted by the browser, **all of its data, not parts of it**, is deleted at the same time." `#C3`: LRU 는 **origin 단위** | 필드 Rule 에 "**애플리케이션 주도** 정리 순서. 브라우저 eviction 은 origin 전량 삭제이므로 이 순서가 적용되지 않는다" 를 추가 | +| `CACHE_STORAGE_CONSTRAINT_DRIFT` | hub `FE-D027` / `DEC-…-BINARY-STORE-001@1` | "Cache Storage는 service worker 호스팅 response cache 전용이다" — 기술 제약처럼 읽힌다 | `mdn-object-url-cache-storage#C3`: "you're not limited to only using it with service workers", `Window.caches` 로 접근 가능 | 결정 자체는 유효(정책 선택). §5.5 또는 `FE-D027` rationale 에 "플랫폼 제약이 아니라 release coherence 를 위한 **정책** 선택" 임을 명시 | +| `NO_GROUND_TRUTH` | `/branch-spec` §2 ca-tmpl 대조 | 명령은 `/home/donghyeon/workspace/ca-tmpl` 의 registry·코드와 대조하라고 요구 | `ca-tmpl` 은 Gradle/Java **백엔드 전용**(`domain-core`·`adapter-web`·`shared-contract`). `package.json`·`vite.config`·`.jsx` 부재. frontend 구현 repo 는 hub §0.3 대로 미식별 | 이 branch 의 모든 명세는 `planned`. `actually-implemented` 승격은 frontend repo 식별(`FE-Q-001`) 이후 | +| `CAPABILITY_NAME_COLLISION` | `ca-tmpl/docs/registries/capabilities.yaml` vs hub `FE-REG-CAPABILITY` | 두 registry 가 같은 "capability" 어휘를 쓴다 | ca-tmpl 은 *use case → infrastructure 접근 권한*(`READ_REPOSITORY` 등), hub 는 *브라우저 런타임 기능 flag*(`CAP_FE_BINARY_IO` 등). **다른 개념** | 계약을 상호 참조하지 않는다. 혼동 방지를 위해 frontend 쪽은 `CAP_FE_` prefix 를 유지 | ## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때) -미생성. `/coverage` 명령이 이 repo 에 없으므로(하네스 삭제) 수기 검토로 대체한다. +> 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 — "하나라도 비어 있으면 branch 는 `documented-only` 를 넘을 수 없다". + +| 관심사 (hub §2.2) | 상태 | owner | 심각도 | 근거 | +|---|---|---|---|---| +| 1. 이 contract 가 막는 concrete failure | covered-here | — | — | §목표 — object URL 누수, quota 초과 시 correctness 값의 조용한 memory fallback | +| 2. input 과 output | covered-here | — | — | §구현 가이드 1(선택 제약 → descriptor)·3(등록 key + 바이너리 descriptor → 저장/조회/삭제 결과) | +| 3. project-wide default 와 limit | covered-here | — | — | D3(기준선 ``), D5(비동기 OPFS 기본), D6(evictionOrder 적용 범위), D7(persist 1회) | +| 4. 허용되는 예외와 승인 owner | covered-here | — | — | §선언한 예외 "해당 없음" + D3·D5 의 선택 조건(브라우저 매트릭스 확정 / 블로킹 측정 시) | +| 5. 금지 구현 | covered-here | — | — | §구현 가이드 2 — raw object URL 문자열 단독 반환 금지, SW 에서 object URL 생성 금지, `evictionOrder: null` 행 정리 금지 | +| 6. failure 가 어떤 normalized error·UX 로 나타나는가 | delegated | `feature-frontend-error-classification-boundary-contract` `D1`·`D3`·`D6` | OK | 본 branch 는 4개 kind 를 *생산*하고 정규화 총함수·UX 어휘는 error-classification 소유 | +| 7. 어떤 telemetry 가 남고 무엇이 redacted 되는가 | covered-here | — | Should-fix | §구현 가이드 1 이 "파일명은 telemetry 로 보내지 않음" 을 명시하나, `FE-REG-TELEMETRY` 에 이 branch 전용 event 를 등록하지 않았다 — quota/eviction 관측 event 부재 | +| 8. 어떤 test 가 위반 시 실패하는가 | covered-here | — | — | §검증해야 할 주장 9행 + `FE-GATE-027` fixture 5종 | +| 9. 어떤 evidence artifact 가 생성되는가 | covered-here | — | — | hub §15.1 `FE-GATE-027` → `binary I-O report` | +| 10. release·rollback 에 미치는 영향 | delegated | `feature-frontend-background-execution-worker-contract` `D2`(`serviceWorkerVersion`) | OK | Cache Storage 파티션이 release 토큰에 묶이며, rollback 시 되돌림 판정은 release branch 소유(`DELEG-FE-011`) | + +**판정: Covered (missing 0)** — `🔴 missing` 0건, `Should-fix` 1건(관심사 7 telemetry event 미등록). ## 마주친 문제 diff --git a/raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md b/raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md index 96f17b3..5f4da48 100644 --- a/raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md +++ b/raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md @@ -17,6 +17,7 @@ contract_packet: 1 branch: feature-frontend-cache-tier-cross-tab-invalidation-contract parent_branch: related_projects: [ca-skeleton-frontend, ca-skeleton] +governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md] tags: [branch, ca-skeleton-frontend, cache, server-state, cross-tab] created: 2026-07-28 target_merge: diff --git a/raw/branch-notes/feature-frontend-large-object-transfer-contract.md b/raw/branch-notes/feature-frontend-large-object-transfer-contract.md index 22edfec..70aa6cd 100644 --- a/raw/branch-notes/feature-frontend-large-object-transfer-contract.md +++ b/raw/branch-notes/feature-frontend-large-object-transfer-contract.md @@ -17,6 +17,7 @@ contract_packet: 1 branch: feature-frontend-large-object-transfer-contract parent_branch: related_projects: [ca-skeleton-frontend, ca-skeleton] +governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md] tags: [branch, ca-skeleton-frontend, transfer, upload, streaming] created: 2026-07-28 target_merge: diff --git a/raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract.md b/raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract.md index a980820..2cb81a0 100644 --- a/raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract.md +++ b/raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract.md @@ -17,6 +17,7 @@ contract_packet: 1 branch: feature-frontend-multi-protocol-api-transport-contract parent_branch: related_projects: [ca-skeleton-frontend, ca-skeleton] +governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md] tags: [branch, ca-skeleton-frontend, api, protocol, graphql, grpc-web] created: 2026-07-28 target_merge: diff --git a/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md b/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md index 4c9c69d..1ec67ef 100644 --- a/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md +++ b/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md @@ -17,6 +17,7 @@ contract_packet: 1 branch: feature-frontend-realtime-subscription-lifecycle-contract parent_branch: related_projects: [ca-skeleton-frontend, ca-skeleton] +governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md] tags: [branch, ca-skeleton-frontend, realtime, sse, websocket, push] created: 2026-07-28 target_merge: diff --git a/raw/official-docs/mdn-file-system-access-opfs.md b/raw/official-docs/mdn-file-system-access-opfs.md new file mode 100644 index 0000000..2784358 --- /dev/null +++ b/raw/official-docs/mdn-file-system-access-opfs.md @@ -0,0 +1,104 @@ +--- +title: MDN — File System API (showOpenFilePicker / showSaveFilePicker / Origin Private File System) +source_type: official-doc +url: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system +archive_url: +related_branches: [feature-frontend-binary-file-io-store-contract, feature-frontend-large-object-transfer-contract] +related_projects: [ca-skeleton-frontend] +tags: [frontend, file-io, opfs, file-system-access, browser] +created: 2026-07-28 +--- + +# MDN — File System API (showOpenFilePicker / showSaveFilePicker / Origin Private File System) + +> Layer: `raw/` — 외부 자료의 **원문 발췌·출처 기록**. + +## 활용 branch (필수, 최소 1개+) + +| Branch | 이 자료가 정당화하는 결정 | +|---|---| +| `[[raw/branch-notes/feature-frontend-binary-file-io-store-contract]]` | `FileDialogPort` 의 메커니즘 선택·fallback 조건, OPFS backend 를 opt-in 으로 두는 근거, picker 취소의 실패 매핑 | +| `[[raw/branch-notes/feature-frontend-large-object-transfer-contract]]` | 대용량 순차 write 를 OPFS 로 처리할 때의 worker 제약 | + +## 출처 + +- 원본 URL: https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system +- 보조 URL: https://developer.mozilla.org/en-US/docs/Web/API/Window/showOpenFilePicker , https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker +- 아카이브 URL: +- 저자 / 조직: MDN Web Docs (Mozilla) +- 발행일: (문서 지속 갱신) +- 마지막 확인일: 2026-07-28 + +## 왜 저장했는지 + +`FileDialogPort` 와 `BlobStorePort` 의 메커니즘을 추측으로 정하지 않기 위해. 특히 (a) File System Access API 를 default 로 삼아도 되는지, (b) OPFS 의 동기 API 가 어느 실행 컨텍스트에서만 가능한지, (c) picker 취소가 어떤 예외로 오는지를 확인했다. + +## 핵심 인용 + +> [§showOpenFilePicker — Baseline] "**Limited availability** — This feature is not Baseline because it does not work in some of the most widely-used browsers." + +> [§showOpenFilePicker] "The `showOpenFilePicker()` method of the `Window` interface shows a file picker that allows a user to select a file or multiple files and returns a handle for the file(s)." + +> [§showOpenFilePicker — Security] "Transient user activation is required. The user has to interact with the page or a UI element in order for this feature to work." + +> [§showOpenFilePicker — Exceptions] "`AbortError` `DOMException` — Thrown if the user dismisses the prompt without making a selection, or if the user agent deems any selected files too sensitive or dangerous." + +> [§showSaveFilePicker] "The `showSaveFilePicker()` method of the `Window` interface shows a file picker that allows a user to save a file. Either by selecting an existing file, or entering a name for a new file." + +> [§showSaveFilePicker — Return value] "A `Promise` whose fulfillment handler receives a `FileSystemFileHandle` object." + +> [§showSaveFilePicker — Exceptions] "`AbortError` `DOMException` — Thrown if the user dismisses the file picker without selecting or inputting a file, or if the user agent deems any selected files too sensitive or dangerous." + +> [§showSaveFilePicker — Secure context] "**Secure context:** This feature is available only in secure contexts (HTTPS), in some or all supporting browsers." + +> [§OPFS] "The **origin private file system** (OPFS) is a storage endpoint provided as part of the File System API, which is private to the origin of the page and not visible to the user like the regular file system." + +> [§OPFS] "The OPFS is subject to browser storage quota restrictions, just like any other origin-partitioned storage mechanism (for example IndexedDB API)." + +> [§OPFS] "Permission prompts and security checks are not required to access files in the OPFS." + +> [§OPFS] "It also has a set of synchronous calls available (other File System API calls are asynchronous) that can be run inside web workers only so as not to block the main thread." + +> [§OPFS] "The OPFS offers low-level, byte-by-byte file access, which is private to the origin of the page and not visible to the user. As a result, it doesn't require the same series of security checks and permission grants and is therefore faster than File System Access API calls." + +> [§OPFS] "Web Workers don't block the main thread, which means you can use the synchronous file access APIs in this context." + +> [§OPFS] "Browsers persist the contents of the OPFS to disk somewhere, but you cannot expect to find the created files matched one-to-one. The OPFS is not intended to be visible to the user." + +> [§OPFS] "Clearing storage data for the site deletes the OPFS." + +## 추출된 주장 + +| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove | +|---|---|---|---|---|---| +| C1 | `showOpenFilePicker()` 는 Baseline 이 아니며 널리 쓰이는 일부 브라우저에서 동작하지 않는다 | [§Baseline] "not Baseline because it does not work in some of the most widely-used browsers" | `official-reference` | File System Access picker 계열 | 어떤 브라우저가 미지원인지 구체 목록 | +| C2 | picker 계열은 transient user activation 을 요구한다 — 사용자 제스처 없이 호출할 수 없다 | [§Security] "Transient user activation is required." | `official-reference` | `showOpenFilePicker`·`showSaveFilePicker` | `` 도 같은 제약을 갖는다는 뜻은 아님 | +| C3 | 사용자가 picker 를 닫으면 `AbortError` DOMException 이 던져진다 | [§Exceptions] "Thrown if the user dismisses the prompt without making a selection" | `official-reference` | picker 계열 | 취소와 "위험한 파일 거부"가 구분되어 온다는 뜻은 아님 — 같은 예외다 | +| C4 | picker 계열은 secure context(HTTPS)에서만 사용 가능하다 | [§Secure context] "available only in secure contexts (HTTPS)" | `official-reference` | picker 계열 | localhost 예외 여부 | +| C5 | OPFS 의 동기 접근 API 는 **web worker 안에서만** 실행할 수 있다 | [§OPFS] "can be run inside web workers only so as not to block the main thread" | `official-reference` | `createSyncAccessHandle()` | 비동기 OPFS API 도 worker 전용이라는 뜻은 아님 | +| C6 | OPFS 는 권한 프롬프트·보안 검사를 요구하지 않아 File System Access API 호출보다 빠르다 | [§OPFS] "it doesn't require the same series of security checks and permission grants and is therefore faster than File System Access API calls" | `official-reference` | OPFS byte 접근 | IndexedDB 보다 빠르다는 뜻은 아님 — 비교 대상은 File System Access API 다 | +| C7 | OPFS 는 다른 origin-partitioned storage 와 동일한 quota 제약을 받는다 | [§OPFS] "subject to browser storage quota restrictions, just like any other origin-partitioned storage mechanism" | `official-reference` | OPFS | OPFS 가 별도 quota 를 갖는다는 뜻은 아님 | +| C8 | OPFS 는 사용자에게 보이지 않으며 사이트 저장소를 지우면 함께 삭제된다 | [§OPFS] "Clearing storage data for the site deletes the OPFS." | `official-reference` | OPFS | 사용자가 개별 파일만 지울 수 있다는 뜻은 아님 | + +## 적용 경계 + +- 이 자료가 직접 증명하는 것: + - `C1`~`C4`: picker 계열의 가용성·활성화 요구·예외·보안 컨텍스트 + - `C5`~`C8`: OPFS 의 실행 컨텍스트 제약, 성능 비교 대상, quota 귀속, 삭제 조건 +- 이 자료가 증명하지 않는 것: + - `` + Blob URL fallback 이 picker 와 **동등**하다는 것 — MDN 은 fallback 이 덮어쓰기를 지원하지 않는다고만 언급한다 + - OPFS 가 IndexedDB 보다 빠르다는 것 (`C6` 의 비교 대상은 File System Access API 다) +- 내 프로젝트에 적용하려면 추가 확인이 필요한 것: + - 대상 브라우저 매트릭스 확정 후 picker 지원 여부 실측 (`FE-Q-007` 브라우저 지원 매트릭스) + - OPFS 순차 write 가 IndexedDB 대비 유리한지의 자체 벤치마크 + +## 메모 + +- 인용 1 해석 후보 (미검증): `C1` 때문에 `FileDialogPort` 는 File System Access 를 **default 로 가정할 수 없고**, `` 을 기준선으로 두고 picker 를 progressive enhancement 로 얹는 구조가 안전하다. +- 인용 2 해석 후보 (미검증): `C5` 는 OPFS 를 쓰려면 `WorkerTaskPort`(`FE-OC-032`)와의 결합이 필요하다는 뜻이 될 수 있다 — 동기 API 를 쓸 경우에 한정. +- 추가로 봐야 할 동일 출처 페이지: `FileSystemFileHandle.createWritable()`, `FileSystemSyncAccessHandle` + +## 관련 + +- 같은 주제 다른 official-doc: `[[raw/official-docs/mdn-storage-quotas-eviction-persistence]]`, `[[raw/official-docs/mdn-object-url-cache-storage]]` +- 이 자료를 인용한 wiki 요약: 생성 전 diff --git a/raw/official-docs/mdn-object-url-cache-storage.md b/raw/official-docs/mdn-object-url-cache-storage.md new file mode 100644 index 0000000..4e8812c --- /dev/null +++ b/raw/official-docs/mdn-object-url-cache-storage.md @@ -0,0 +1,84 @@ +--- +title: MDN — URL.createObjectURL() / CacheStorage +source_type: official-doc +url: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static +archive_url: +related_branches: [feature-frontend-binary-file-io-store-contract, feature-frontend-background-execution-worker-contract] +related_projects: [ca-skeleton-frontend] +tags: [frontend, blob, object-url, cache-storage, service-worker, browser] +created: 2026-07-28 +--- + +# MDN — URL.createObjectURL() / CacheStorage + +> Layer: `raw/` — 외부 자료의 **원문 발췌·출처 기록**. + +## 활용 branch (필수, 최소 1개+) + +| Branch | 이 자료가 정당화하는 결정 | +|---|---| +| `[[raw/branch-notes/feature-frontend-binary-file-io-store-contract]]` | object URL 수명 소유 결정(`D1`), Cache Storage backend 의 실행 컨텍스트 전제 | +| `[[raw/branch-notes/feature-frontend-background-execution-worker-contract]]` | service worker 안에서 object URL 을 만들 수 없다는 제약, Cache Storage 버전 파티션 | + +## 출처 + +- 원본 URL: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static +- 보조 URL: https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage +- 아카이브 URL: +- 저자 / 조직: MDN Web Docs (Mozilla) +- 발행일: (문서 지속 갱신) +- 마지막 확인일: 2026-07-28 + +## 왜 저장했는지 + +(a) object URL 을 adapter 가 쌍으로 소유해야 한다는 결정에 외부 근거가 있는지, (b) Cache Storage 가 service worker 전용인지를 확인하기 위해. (b) 는 `FE-D027` 의 "Cache Storage 는 service worker 호스팅 response cache 전용" 서술이 **기술 제약인지 정책인지** 가르는 지점이었다. + +## 핵심 인용 + +> [§URL.createObjectURL] "To release an object URL, call `revokeObjectURL()`." + +> [§URL.createObjectURL — Availability] "This feature is _not_ available in Service Workers due to its potential to create memory leaks." + +> [§CacheStorage] "The `CacheStorage` interface represents the storage for `Cache` objects." + +> [§CacheStorage] "Provides a master directory of all the named caches that can be accessed by a `ServiceWorker` or other type of worker or `window` scope (you're not limited to only using it with service workers)." + +> [§CacheStorage] "You can access `CacheStorage` through the `Window.caches` property in windows or through the `WorkerGlobalScope.caches` property in workers." + +> [§CacheStorage — Examples] "Delete any old caches to respect user's disk space." + +> [§CacheStorage.delete] "Finds the `Cache` object matching the `cacheName`, and if found, deletes the `Cache` object and returns a `Promise` that resolves to `true`. If no `Cache` object is found, it resolves to `false`." + +> [§CacheStorage.keys] "Returns a `Promise` that will resolve with an array containing strings corresponding to all of the named `Cache` objects tracked by the `CacheStorage`. Use this method to iterate over a list of all the `Cache` objects." + +## 추출된 주장 + +| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove | +|---|---|---|---|---|---| +| C1 | object URL 은 명시적으로 `revokeObjectURL()` 을 호출해야 해제된다 | [§createObjectURL] "To release an object URL, call `revokeObjectURL()`." | `official-reference` | 모든 object URL | 해제하지 않으면 반드시 누수가 된다는 정량적 근거 | +| C2 | `createObjectURL` 은 **메모리 누수 가능성 때문에** Service Worker 에서 제공되지 않는다 | [§Availability] "not available in Service Workers due to its potential to create memory leaks" | `official-reference` | Service Worker 컨텍스트 | window/worker 에서 누수가 발생하지 않는다는 뜻은 아님 — 오히려 같은 위험의 존재를 시사 | +| C3 | `CacheStorage` 는 service worker 전용이 아니며 window scope 에서도 접근 가능하다 | [§CacheStorage] "you're not limited to only using it with service workers" | `official-reference` | `Window.caches`·`WorkerGlobalScope.caches` | service worker 없이 오프라인 응답 가로채기가 가능하다는 뜻은 아님 | +| C4 | MDN 예제는 캐시 이름에 버전을 넣고 오래된 캐시를 삭제하는 패턴을 제시한다 | [§Examples] "Delete any old caches to respect user's disk space." (예제: `` `myapp-${cacheVersion}` ``) | `official-reference` | Cache Storage 운영 | 특정 버전 문자열 형식이 표준이라는 뜻은 아님 | +| C5 | `CacheStorage.keys()` 로 캐시 목록을 열거하고 `delete(cacheName)` 로 개별 캐시를 지울 수 있다 | [§CacheStorage.keys] "Use this method to iterate over a list of all the `Cache` objects." | `official-reference` | Cache Storage | 개별 응답 단위 eviction 순서를 브라우저가 보장한다는 뜻은 아님 | + +## 적용 경계 + +- 이 자료가 직접 증명하는 것: + - `C1`·`C2`: object URL 의 해제 책임과 Service Worker 미제공 사실 + - `C3`~`C5`: Cache Storage 의 접근 범위와 버전 파티션 운영 수단 +- 이 자료가 증명하지 않는 것: + - object URL 을 **adapter 가** 소유해야 한다는 것 — MDN 은 해제 필요성만 말하고 소유 주체는 말하지 않는다. 소유 주체는 project decision 이다 + - Cache Storage 를 service worker 로 제한하는 것이 필요하다는 것 — 오히려 `C3` 은 그 반대를 말한다 +- 내 프로젝트에 적용하려면 추가 확인이 필요한 것: + - `FE-D027` 의 "Cache Storage = SW 호스팅 전용" 이 기술 제약이 아니라 **정책**임을 문서에 명시할 것 + +## 메모 + +- 인용 1 해석 후보 (미검증): `C2` 는 "Service Worker 에서 못 쓴다" 는 가용성 사실이지만, 그 **사유**가 memory leak 이라는 점에서 window 컨텍스트에서도 해제 규율이 필요하다는 방증으로 읽을 수 있다. 다만 이는 해석이며 MDN 이 직접 말한 것은 아니다. +- 인용 2 해석 후보 (미검증): `C3` 때문에 `FE-D027` 의 Cache Storage 서술은 `CATEGORY_DRIFT` 성격의 정정이 필요하다 — 기술적으로 불가능한 것이 아니라 우리가 그렇게 **정한** 것이다. +- 추가로 봐야 할 동일 출처 페이지: `URL.revokeObjectURL()`, `Cache.put()` 의 quota 동작 + +## 관련 + +- 같은 주제 다른 official-doc: `[[raw/official-docs/mdn-storage-quotas-eviction-persistence]]`, `[[raw/official-docs/mdn-file-system-access-opfs]]` +- 이 자료를 인용한 wiki 요약: 생성 전 diff --git a/raw/official-docs/mdn-storage-quotas-eviction-persistence.md b/raw/official-docs/mdn-storage-quotas-eviction-persistence.md new file mode 100644 index 0000000..234b94f --- /dev/null +++ b/raw/official-docs/mdn-storage-quotas-eviction-persistence.md @@ -0,0 +1,94 @@ +--- +title: MDN — Storage quotas and eviction criteria / StorageManager.persist() +source_type: official-doc +url: https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria +archive_url: +related_branches: [feature-frontend-binary-file-io-store-contract, feature-frontend-large-object-transfer-contract, feature-frontend-cache-tier-cross-tab-invalidation-contract] +related_projects: [ca-skeleton-frontend] +tags: [frontend, storage, indexeddb, opfs, cache-storage, quota, browser] +created: 2026-07-28 +--- + +# MDN — Storage quotas and eviction criteria / StorageManager.persist() + +> Layer: `raw/` — 외부 자료의 **원문 발췌·출처 기록**. + +## 활용 branch (필수, 최소 1개+) + +| Branch | 이 자료가 정당화하는 결정 | +|---|---| +| `[[raw/branch-notes/feature-frontend-binary-file-io-store-contract]]` | `BlobStorePort` 의 quota 실패 매핑, eviction 정책, `evictionOrder` 필드의 실제 적용 범위 | +| `[[raw/branch-notes/feature-frontend-large-object-transfer-contract]]` | `UPLOAD_PART_STATE` 를 재개 가능 전송의 correctness 값으로 유지할 수 있는지의 조건 | +| `[[raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract]]` | 영속 캐시 tier 가 브라우저 eviction 에 노출되는 범위 | + +## 출처 + +- 원본 URL: https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria +- 보조 URL: https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/persist +- 아카이브 URL: +- 저자 / 조직: MDN Web Docs (Mozilla) +- 발행일: (문서 지속 갱신) +- 마지막 확인일: 2026-07-28 + +## 왜 저장했는지 + +`ca-skeleton-frontend` 의 `FE-REG-STORAGE` 가 `quotaFallback`·`evictionOrder` 필드를 정의했는데, 그 필드가 **브라우저 eviction 에도 적용되는지** 확인이 필요했다. 결과적으로 브라우저 eviction 은 origin 단위 전량 삭제라 per-key 순서가 적용되지 않는다는 사실을 확인했고, 이는 `FE-D027`·`FE-D030` 의 전제를 바꾼다. + +## 핵심 인용 + +> [§Storage limits — Firefox] "In Firefox, the maximum storage space an origin can use in best-effort mode is whichever is the smaller of: 10% of the total disk size where the profile of the user is stored. Or 10 GiB, which is the _group limit_ that Firefox applies to all origins that are part of the same site." + +> [§Storage limits — Chromium] "In browsers based on the Chromium open-source project, including Chrome and Edge, an origin can store up to 60% of the total disk size in both persistent and best-effort modes." + +> [§Storage limits — Web Storage] "Web Storage, which can be accessed by using the `localStorage` and `sessionStorage` properties of the `window` object, is limited to 10 MiB of data maximum on all browsers." + +> [§Storage limits — Web Storage] "Browsers can store up to 5 MiB of local storage, and 5 MiB of session storage per origin." + +> [§Eviction] "Browsers use a Least Recently Used (LRU) policy to deal with this scenario. The data from the least recently used origin is deleted. If storage pressure continues, the browser moves on to the second least recently used origin, and so on, until the problem is resolved." + +> [§Eviction] "When an origin's data is evicted by the browser, all of its data, not parts of it, is deleted at the same time. If the origin had stored data by using IndexedDB and the Cache API for example, then both types of data are deleted." + +> [§Eviction] "This eviction mechanism only applies to origins that are not persistent and skips over origins that have been granted data persistence by using `navigator.storage.persist()`." + +> [§Eviction — Safari] "Safari proactively evicts data when cross-site tracking prevention is turned on. If an origin has no user interaction, such as click or tap, in the last seven days of browser use, its data created from script will be deleted." + +> [§Persistence] "Best-effort: this is the way that data is stored by default. Best-effort data persists as long as the origin is below its quota, the device has enough storage space, and the user doesn't choose to delete the data via their browser's settings." + +> [§Persistence] "Persistent: an origin can opt-in to store its data in a persistent way. Data stored this way is only evicted, or deleted, if the user chooses to, by using their browser's settings." + +> [§StorageManager.persist] "The `persist()` method of the `StorageManager` interface requests permission to use persistent storage, and returns a `Promise` that resolves to `true` if permission is granted and bucket mode is persistent, and `false` otherwise." + +> [§StorageManager.persist] "The browser may or may not honor the request, depending on browser-specific rules." + +## 추출된 주장 + +| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove | +|---|---|---|---|---|---| +| C1 | 브라우저 eviction 은 origin 단위로 **전량** 삭제하며 일부만 지우지 않는다 | [§Eviction] "all of its data, not parts of it, is deleted at the same time" | `official-reference` | best-effort 모드의 모든 origin-partitioned storage | 애플리케이션이 스스로 수행하는 선택적 정리까지 금지한다는 뜻은 아님 | +| C2 | IndexedDB 와 Cache API 는 같은 eviction 단위에 속해 함께 삭제된다 | [§Eviction] "If the origin had stored data by using IndexedDB and the Cache API for example, then both types of data are deleted" | `official-reference` | IndexedDB · Cache API · File System API(OPFS) | 각 API 의 개별 quota 가 존재한다는 뜻은 아님 | +| C3 | eviction 은 LRU origin 순서로 일어난다 | [§Eviction] "Browsers use a Least Recently Used (LRU) policy... The data from the least recently used origin is deleted" | `official-reference` | best-effort origin | origin **내부**의 key 우선순위를 브라우저가 존중한다는 뜻은 아님 | +| C4 | `navigator.storage.persist()` 로 허가받은 origin 은 LRU eviction 대상에서 제외된다 | [§Eviction] "skips over origins that have been granted data persistence by using `navigator.storage.persist()`" | `official-reference` | 허가가 실제로 부여된 origin | 요청하면 항상 허가된다는 뜻은 아님 | +| C5 | persist 요청의 허가 여부는 브라우저 재량이며 boolean 으로 반환된다 | [§StorageManager.persist] "The browser may or may not honor the request, depending on browser-specific rules." | `official-reference` | 모든 지원 브라우저 | 특정 브라우저의 허가 조건이 무엇인지 | +| C6 | Web Storage(`localStorage`/`sessionStorage`)는 origin 당 5 MiB 수준이며 IndexedDB 계열과 별도 한도를 가진다 | [§Storage limits] "Browsers can store up to 5 MiB of local storage, and 5 MiB of session storage per origin." | `official-reference` | 모든 브라우저 | Web Storage 가 eviction 에서 제외된다는 뜻은 아님 | +| C7 | Safari 는 cross-site tracking prevention 활성 시 7일간 사용자 상호작용이 없으면 script 로 만든 데이터를 삭제한다 | [§Eviction — Safari] "If an origin has no user interaction... in the last seven days of browser use, its data created from script will be deleted." | `official-reference` | Safari + ITP 활성 | 다른 브라우저도 같은 정책을 쓴다는 뜻은 아님 | + +## 적용 경계 + +- 이 자료가 직접 증명하는 것: + - `C1`~`C3`: 브라우저 주도 eviction 의 단위와 순서 + - `C4`·`C5`: persist() 의 효과와 불확실성 +- 이 자료가 증명하지 않는 것: + - 애플리케이션이 quota 압박 시 **스스로** 어떤 순서로 정리해야 하는지 — 그건 제품 결정이다 + - 특정 브라우저가 persist 를 허가하는 구체 조건 +- 내 프로젝트에 적용하려면 추가 확인이 필요한 것: + - `UPLOAD_PART_STATE` 가 재개 가능 전송에 실제로 충분히 오래 살아남는지 (Safari 7일 규칙 + persist 미허가 조합) + +## 메모 + +- 인용 1 해석 후보 (미검증): `FE-REG-STORAGE.evictionOrder` 는 **애플리케이션 주도 정리 순서**로만 유효하다. 브라우저 eviction 에는 적용되지 않으므로 필드 설명에 그 경계를 명시해야 한다. +- 추가로 봐야 할 동일 출처 페이지: `StorageManager.estimate()`, `navigator.storage.persisted()` + +## 관련 + +- 같은 주제 다른 official-doc: `[[raw/official-docs/mdn-file-system-access-opfs]]`, `[[raw/official-docs/mdn-object-url-cache-storage]]` +- 이 자료를 인용한 wiki 요약: 생성 전