From d1ae257efe615efdb17c0bcad76b441163ff5e9e Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Tue, 28 Jul 2026 14:36:43 +0900 Subject: [PATCH] =?UTF-8?q?docs(branch):=20=EB=9F=B0=ED=83=80=EC=9E=84=20c?= =?UTF-8?q?apability=20branch-note=206=EA=B0=9C=20=EC=8A=A4=EC=BA=90?= =?UTF-8?q?=ED=8F=B4=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 가 늘어나면 그 자체가 회귀 신호다 --- ...nd-background-execution-worker-contract.md | 260 +++++++++++++++++ ...-frontend-binary-file-io-store-contract.md | 239 ++++++++++++++++ ...he-tier-cross-tab-invalidation-contract.md | 241 ++++++++++++++++ ...frontend-large-object-transfer-contract.md | 247 ++++++++++++++++ ...d-multi-protocol-api-transport-contract.md | 248 ++++++++++++++++ ...ealtime-subscription-lifecycle-contract.md | 266 ++++++++++++++++++ 6 files changed, 1501 insertions(+) create mode 100644 raw/branch-notes/feature-frontend-background-execution-worker-contract.md create mode 100644 raw/branch-notes/feature-frontend-binary-file-io-store-contract.md create mode 100644 raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md create mode 100644 raw/branch-notes/feature-frontend-large-object-transfer-contract.md create mode 100644 raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract.md create mode 100644 raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md diff --git a/raw/branch-notes/feature-frontend-background-execution-worker-contract.md b/raw/branch-notes/feature-frontend-background-execution-worker-contract.md new file mode 100644 index 0000000..a79ac97 --- /dev/null +++ b/raw/branch-notes/feature-frontend-background-execution-worker-contract.md @@ -0,0 +1,260 @@ +--- +title: branch / feature-frontend-background-execution-worker-contract +source_type: branch-note +status: raw +id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-033 +kind: project-work-item +project: ca-skeleton-frontend-operational-contract +work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-033 +inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVICE-WORKER-ROLE-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BACKGROUND-SYNC-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-WORKER-TASK-001@1] +refines: [] +overrides: [] +depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-024] +imports: [FE-GATE-032@1, FE-OC-009@1, FE-OC-016@1, FE-OC-017@1, FE-OC-025@1] +delegates: [DELEG-FE-011] +accepts_delegations: [DELEG-FE-007] +contract_packet: 1 +branch: feature-frontend-background-execution-worker-contract +parent_branch: +related_projects: [ca-skeleton-frontend, ca-skeleton] +tags: [branch, ca-skeleton-frontend, worker, service-worker, background-sync] +created: 2026-07-28 +target_merge: +status_label: in-progress +--- + +# branch: feature-frontend-background-execution-worker-contract + + +## 부모 (필수) + +- [[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-large-object-transfer-contract]] +- [[raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract]] +- [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]] + + +## 브랜치 계약 패킷 + +- **생성 시 프로젝트 개정**: `2` +- **패킷 스키마**: `contract_packet: 1` +- **완료 조건**: SW update UX·rollback SW 되돌림·sync idempotency·worker timeout fixture와 `FE-RB-007` drill이 통과한다 + + +### 상속한 프로젝트 결정 + +| 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_BACKGROUND_EXEC` 를 이 branch 가 소유하고 default OFF 로 유지한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVICE-WORKER-ROLE-001@1` | service worker는 역할을 분리해 release asset precaching은 default off로 유지하고 push·background sync·Cache Storage 호스트 역할만 capability opt-in으로 허용하며 update UX 계약을 요구한다 | `ServiceWorkerHostPort` 의 등록 범위와 §9.7 update surface state 에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BACKGROUND-SYNC-001@1` | background sync 재생은 idempotency keyed operation만 허용한다 | `BackgroundSyncPort` 의 큐 등록 조건에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-WORKER-TASK-001@1` | Web Worker 작업은 structured-clone 또는 Transferable로만 통신하고 timeout과 terminate를 계약하며 worker 안에서 application port를 재구현하지 않는다 | `WorkerTaskPort` 의 통신·수명 계약에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | + + +### 브랜치 지역 결정 + +| Decision ID | Decision | Relation | Supporting Claims | Status | +|---|---|---|---|---| +| D1 | `skipWaiting` 을 자동 호출하지 않는다. 새 SW 활성화는 사용자 주도 action 으로만 수행한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVICE-WORKER-ROLE-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D2 | `serviceWorkerVersion` 을 release token 으로 산출해 rollback 시 SW 되돌림 여부를 판정 가능하게 한다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D3 | worker 엔트리(`src/workers/*.worker.js`)와 `public/sw.js` 는 `presentation`·`application`·`domain` 을 import 하지 않는다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-WORKER-TASK-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | + + +### 선언한 예외 + +> `FE-D019`("service worker와 offline asset cache는 default off") 는 `FE-D034` 로 **superseded** 되었다. 이 branch 는 그 후속 결정의 owner 이며, 아래는 예외 선언이 아니라 대체 사실의 기록이다. + +| Override ID | Overrides | Reason | Approval | Status | +|---|---|---|---|---| +| — | — | 예외 없음. `DEC-...-OFFLINE-CACHE-001@2` 를 그대로 소비한다 | — | — | + +`DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-OFFLINE-CACHE-001@2` 의 소비자는 [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] 이며, 이 branch 는 그 결정을 `SERVICE-WORKER-ROLE-001@1` 로 상속받아 SW 쪽 절반을 구현한다. precaching 절반은 여전히 release branch 소유이자 default off 다. + + +### 가져온 artifact 계약 + +| Artifact Ref | Owner | Producer | Schema Ref | +|---|---|---|---| + + + +## 가져온 프로젝트 계약 + +| Ref | Owner | 요약 | Branch 적용 | +|---|---|---|---| +| `FE-GATE-032@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | SW update UX·sync idempotency·worker timeout fixture 가 실패하면 merge·release 를 MUST 차단 | 이 branch 가 owner 로서 fixture 와 report 를 산출 | +| `FE-OC-009@1` | [[raw/branch-notes/feature-api-client-response-envelope-contract]] | retry는 safe/idempotent request에 한정하고 cap·jitter·`Retry-After`를 MUST 적용 | 지연 재생도 같은 idempotency 조건을 따름 | +| `FE-OC-016@1` | [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] | HTML, asset, runtime config, release manifest cache policy를 MUST 구분 | SW 가 이 정책을 훼손하지 않음 | +| `FE-OC-017@1` | [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] | rollback은 immutable prior release로 수행하고 build/config/API compatibility를 MUST 검증 | `serviceWorkerVersion` 을 compatibility 판정에 추가 (`DELEG-FE-011`) | +| `FE-OC-025@1` | [[raw/branch-notes/feature-frontend-operational-runbook-contract]] | boot, chunk mismatch, API degradation, telemetry failure, rollback, realtime 연결, background 실행 runbook을 MUST 유지 | `FE-RB-007` 의 기술 escalation 대상 | + + + +### 수신한 위임 + +| Delegation Ref | From | Concern | Status | +|---|---|---|---| +| `DELEG-FE-007` | `feature-frontend-realtime-subscription-lifecycle-contract` | WebPush 가 요구하는 service worker 등록·수명주기 호스팅 | accepted | + + + +### 가져온 흐름 단계 + +| Stage Ref | Order | Owner | Input | Action | Output | +|---|---:|---|---|---|---| + + + +## 목표 + +`WorkerTaskPort`·`ServiceWorkerHostPort`·`BackgroundSyncPort` 를 정의하고, `FE-D034` 의 SW 역할 분리를 구현 계약으로 내린다. 이 계약이 없으면 Background Sync 가 idempotency key 없는 mutation 을 재생해 중복 생성하고, SW precache 가 이전 release 자산을 붙들어 `FE-OC-016`·`FE-OC-017` 의 release coherence 를 깬다. + +- 이슈: +- PR: + + +## 범위 + +### 포함 범위 + +- `WorkerTaskPort` — worker 생성·통신(structured-clone/Transferable)·timeout·terminate +- `ServiceWorkerHostPort` — SW 등록·갱신 상태 스트림, §9.7 update surface state +- `BackgroundSyncPort` — keyed mutation 만 큐 등록 +- `serviceWorkerVersion` release token 산출과 rollback 판정 입력 제공 +- worker/SW 엔트리 파일의 import 경계 강제 +- `FE-RB-007` 의 기술 escalation +- `CAP_FE_BACKGROUND_EXEC` capability 행 소유 +- `DELEG-FE-007` 수신 — WebPush 용 SW 호스팅 + +### 제외 범위 + +- **use case, domain model, business rule** — port 와 adapter 계약까지만 정의한다 +- **release asset precaching** — `FE-D034` 에 의해 default off 로 유지되며 [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] 소유 +- rollback **판정** 자체 — `DELEG-FE-011` 로 위임. 이 branch 는 `serviceWorkerVersion` 입력만 제공 +- push 발송 서버·VAPID 개인키 — `FE-Q-014` +- push 구독의 UX·권한 요청 흐름 — [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]] 의 `PushSubscriptionPort` 소유 +- worker 안에서 수행할 도메인 계산 규칙 + +## 근거 (필수, 최소 1개+) + +| Source | 정당화하는 결정 | +|---|---| +| `[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]]` §5.1·§5.4·§9.4 | port 분해, worker/SW 엔트리 import 경계, SW update surface state | +| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.7·§12.5 | idempotency 계약과 rollback invariant | + +**근거 등급 경계**: `FE-D034`(SW 역할 분리)의 rationale 은 `precaching은 release coherence 훼손 원인이나 push·sync는 SW 없이는 불가; project decision` 이고, `FE-D035`·`FE-D036` 은 각각 `FE-D016` 상속과 project decision 이다. Service Worker·Background Sync·Web Push 명세를 다룬 raw 자료는 이 repo 에 없다(`FE-Q-011`). `skipWaiting` 금지는 명세가 아니라 `FE-OC-016` coherence 에서 도출한 project 판단이다. + +## TODO + +- [ ] `WorkerTaskPort` 인터페이스와 timeout·terminate 계약 확정 — 등급: `planned` +- [ ] `ServiceWorkerHostPort` 인터페이스와 등록 scope 확정 — 등급: `planned` +- [ ] `BackgroundSyncPort` 의 keyed-only 등록 조건 — 등급: `planned` +- [ ] §9.7 update surface state 와 사용자 주도 적용 action — 등급: `planned` +- [ ] `serviceWorkerVersion` 토큰 산출과 release manifest 연결 — 등급: `planned` +- [ ] worker/SW 엔트리 import 경계 lint fixture — 등급: `planned` +- [ ] rollback 시 SW 되돌림 fixture — 등급: `planned` +- [ ] `FE-RB-007` drill assertion — 등급: `planned` +- [ ] `FE-GATE-032` background execution report 산출 — 등급: `planned` + +## 진행 중 메모 + +`FE-D019` 를 되살리려는 압력이 나중에 생길 수 있다 — "이왕 SW 있으니 자산도 캐시하자". 그 순간 `FE-OC-016`·`FE-OC-017` 이 다시 위태로워진다. precaching 을 켜려면 `FE-D034` 를 다시 supersede 하는 절차(§3.3)를 밟아야 하며, 그때 update UX 와 rollback drill 이 함께 설계되어야 한다는 조건이 `FE-D034` 의 revisit trigger 에 이미 적혀 있다. + +## 결정 사항 + +- 2026-07-28: 자동 `skipWaiting` 금지 / 이유: 열린 탭이 release 를 갈아타면 HTML·asset·config 가 섞임 / 검토한 대안: 유휴 시 자동 적용 / 근거: `FE-OC-016` coherence 에서 도출, 근거 raw 미수집 +- 2026-07-28: background sync 는 keyed only / 이유: 지연 재생은 중복 write 위험이 온라인 재시도보다 큼 / 검토한 대안: 모든 mutation 재생 + 서버 dedup 신뢰 / 근거: `FE-D035` (`FE-D016` 상속) + + +## 결정-근거 매핑 + +| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | +|---|---|---|---|---|---| +| D1 | 자동 `skipWaiting` 금지 | 항상. 모든 탭이 단명하는 제품이면 재검토 | `FE-OC-016` coherence 도출 | `project decision` | 사용자가 적용 action 을 누르지 않으면 구버전이 오래 남음 | +| D2 | `serviceWorkerVersion` release token | SW 를 쓸 때. 안 쓰면 토큰은 비어 있음 | 없음 — `FE-Q-011` | `project-local default` | release manifest 스키마가 커짐 | +| D3 | worker/SW 엔트리의 import 경계 | 항상 | 없음 — `FE-Q-011` | `project-local default` | 공유 로직을 복제하게 될 수 있음 — `domain` 순수 모듈 참조로 완화 | + + +## 구현 가이드 + +> 근거 raw 자료(`FE-Q-011`) 수집 전까지 비워 둔다. SW 수명주기(installing→waiting→active)와 Background Sync 의 재시도 정책은 명세를 읽지 않고 쓸 수 없다. + + +## 엣지·실패·의존 + +- **실패·엣지 경로** + - SW 등록·갱신 실패 → `SW_REGISTRATION_FAILED`, SW 없이 계속. **제품 흐름을 차단하지 않음** + - 새 SW 설치 완료 → `sw-update-pending`. 실패가 아니라 상태이며 error kind 가 아님 + - Background Sync API 미지원 → `BACKGROUND_SYNC_UNSUPPORTED`, 온라인 복귀 시 전면 재시도로 대체 + - keyed 아닌 mutation 의 재생 시도 → `BACKGROUND_SYNC_REPLAY_REJECTED`, 큐에서 제거하고 재생하지 않음 + - Worker 생성 불가 → `WORKER_UNAVAILABLE`, 메인 스레드 대체 경로 또는 기능 저하 + - worker task timeout → `WORKER_TASK_TIMEOUT`, terminate 후 결과를 기다리지 않음 + - rollback 후 SW 버전 불일치 → `FE-RB-007` 절차. `serviceWorkerVersion` 을 함께 되돌림 +- **다른 계약 의존** + - [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] 의 rollback 판정에 의존 (`DELEG-FE-011` 로 위임) + - [[raw/branch-notes/feature-api-client-response-envelope-contract]] 의 idempotency 계약에 의존 — keyed 판정 기준 + - [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]] 가 이 branch 의 SW 호스팅에 의존 (`DELEG-FE-007`) + - [[raw/branch-notes/feature-frontend-binary-file-io-store-contract]] 의 Cache Storage backend 는 이 branch 의 SW 등록 전제 + + +## 검증해야 할 주장 + +| Claim | Why uncertain | How to verify | Status | +|---|---|---|---| +| `skipWaiting` 이 자동 호출되지 않는다 | 라이브러리 기본값이 자동인 경우가 많음 | grep + fixture — SW 스크립트에 무조건 `skipWaiting` 호출 0건 | `planned` | +| keyed 아닌 mutation 이 replay 되지 않는다 | 큐 등록 시점에 idempotency 를 안 보고 넣기 쉬움 | negative fixture — `idempotency: none` operation 등록 시도가 거부되는지 | `planned` | +| rollback 후 SW 버전이 target release 와 일치한다 | SW 는 별도 수명주기를 가져 뒤처지기 쉬움 | drill — rollback 후 `serviceWorkerVersion` 대조 (`FE-RB-007`) | `planned` | +| worker task 가 timeout 후 terminate 된다 | terminate 없이 결과만 무시하면 스레드가 남음 | fixture — timeout 후 worker 인스턴스 수 확인 (`FE-NFR-019`) | `planned` | +| SW 등록 실패가 제품 흐름을 막지 않는다 | boot 경로에 넣으면 막힘 | fixture — 등록 실패 강제 후 route mount 성공 확인 | `planned` | +| worker/SW 엔트리가 상위 layer 를 import 하지 않는다 | 번들러가 조용히 끌어옴 | dependency-cruiser fixture — 금지 import 검출 | `planned` | +| SW 활성화가 열린 탭의 release 를 바꾸지 않는다 | `clients.claim()` 이 같은 문제를 일으킴 | integration test — 활성화 후 기존 탭의 asset 버전 유지 확인 | `needs-confirmation` | + +## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때) + +미생성. + +## 마주친 문제 + +없음. + +## 묶음 (이 branch에서 파생된 자료) + +### Sub-branches (세부 작업) + +아직 없음. + +### 오류 기록 (이 branch 작업 중 발생) + +아직 없음. + +### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) + +아직 없음. + +### 강의 (이 작업을 위해 학습한 강의) + +아직 없음. + +### job-posting tie-ins (이 작업에서 파생된 글감) + +아직 없음. + +## 관련 일일 노트 + +- 아직 없음 + +## 완료 후 정리 + +- PR 링크: +- 리뷰 메모: +- 머지 결과 / 배포 환경: +- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): + - `actually-implemented` 항목: 없음 + - `locally-verified` 항목: 없음 + - `prod-verified` 항목: 없음 +- **추출하지 않을 항목**: 현재 전 항목 `planned` 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 new file mode 100644 index 0000000..a63d342 --- /dev/null +++ b/raw/branch-notes/feature-frontend-binary-file-io-store-contract.md @@ -0,0 +1,239 @@ +--- +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 + + +## 부모 (필수) + +- [[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]] + + +## 브랜치 계약 패킷 + +- **생성 시 프로젝트 개정**: `2` +- **패킷 스키마**: `contract_packet: 1` +- **완료 조건**: picker·다운로드·object URL 해제·quota·OPFS·Cache Storage fixture가 통과하고 binary I-O report가 생성된다 + + +### 상속한 프로젝트 결정 + +| 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]] | + + +### 브랜치 지역 결정 + +| 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` | + + +### 선언한 예외 + +해당 없음. inherited decision 과 다른 동작을 요구하지 않는다. + + +### 가져온 artifact 계약 + +| Artifact Ref | Owner | Producer | Schema Ref | +|---|---|---|---| + + + +## 가져온 프로젝트 계약 + +| 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 기록 | + + + +### 수신한 위임 + +| Delegation Ref | From | Concern | Status | +|---|---|---|---| +| `DELEG-FE-008` | `feature-frontend-large-object-transfer-contract` | 전송 대상 `File`/`Blob` handle 과 object URL 수명 소유 | accepted | + + + +### 가져온 흐름 단계 + +| Stage Ref | Order | Owner | Input | Action | Output | +|---|---:|---|---|---|---| + + + +## 목표 + +`FileDialogPort` 와 `BlobStorePort` 를 정의하고, IndexedDB·OPFS·Cache Storage adapter 와 object URL 수명 계약을 고정한다. 이 계약이 없으면 컴포넌트가 `URL.createObjectURL` 을 직접 부르고 `revokeObjectURL` 을 빠뜨려 탭 수명 동안 메모리가 증가하며, quota 초과 시 correctness 값이 조용히 memory 로 fallback 된다. + +- 이슈: +- PR: + + +## 범위 + +### 포함 범위 + +- `FileDialogPort` — 파일 선택(`` / 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`) + + +## 결정-근거 매핑 + +| 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 별 성능 특성이 크게 다르면 추상화가 새는 지점이 생김 | + + +## 구현 가이드 + +> 근거 raw 자료(`FE-Q-011`) 수집 전까지 비워 둔다. 지금 채우면 모든 cell 이 `UNSUPPORTED_IMPL_DECISION` 이 되어, 다음 작업자가 *근거 있는 결정* 과 *임의 trade-off* 를 구분할 수 없다. + + +## 엣지·실패·의존 + +- **실패·엣지 경로** + - 사용자가 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 가 총함수로 매핑되어야 함 + + +## 검증해야 할 주장 + +| 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` 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 new file mode 100644 index 0000000..56c9c46 --- /dev/null +++ b/raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md @@ -0,0 +1,241 @@ +--- +title: branch / feature-frontend-cache-tier-cross-tab-invalidation-contract +source_type: branch-note +status: raw +id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-029 +kind: project-work-item +project: ca-skeleton-frontend-operational-contract +work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-029 +inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CROSS-TAB-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVER-STATE-001@1] +refines: [] +overrides: [] +depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-010, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-011] +imports: [FE-GATE-028@1, FE-OC-012@1, FE-OC-013@1, FE-OC-023@1] +delegates: [DELEG-FE-009] +accepts_delegations: [] +contract_packet: 1 +branch: feature-frontend-cache-tier-cross-tab-invalidation-contract +parent_branch: +related_projects: [ca-skeleton-frontend, ca-skeleton] +tags: [branch, ca-skeleton-frontend, cache, server-state, cross-tab] +created: 2026-07-28 +target_merge: +status_label: in-progress +--- + +# branch: feature-frontend-cache-tier-cross-tab-invalidation-contract + + +## 부모 (필수) + +- [[raw/project-notes/ca-skeleton-frontend-operational-contract]] + +형제 branch (같은 부모, 이번 확장에서 함께 생성): + +- [[raw/branch-notes/feature-frontend-binary-file-io-store-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]] + + +## 브랜치 계약 패킷 + +- **생성 시 프로젝트 개정**: `2` +- **패킷 스키마**: `contract_packet: 1` +- **완료 조건**: version 파티션·탭 간 무효화·채널 부재 fallback 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_CACHE_PERSISTENCE` 를 이 branch 가 소유하고 default OFF 로 유지한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CROSS-TAB-001@1` | 탭 간 무효화는 BroadcastChannel 우선에 storage event fallback을 쓰고 leader election 없이 무효화 key만 전파한다 | `CrossTabSyncPort` adapter 의 transport 선택과 메시지 봉투에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVER-STATE-001@1` | application-owned QueryCachePort와 TanStack Query adapter를 사용하고 client store에 server state를 복제하지 않는다 | 영속 tier 를 추가해도 `QueryCachePort` 를 우회하지 않는다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | + + +### 브랜치 지역 결정 + +| Decision ID | Decision | Relation | Supporting Claims | Status | +|---|---|---|---|---| +| D1 | 영속 캐시 파티션 키는 `releaseId`·`configSchemaVersion`·`apiContractVersion` 세 값을 모두 포함하고 하나라도 불일치하면 복원하지 않고 폐기한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVER-STATE-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D2 | 탭 간 메시지는 무효화 key 만 싣고 값을 싣지 않는다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CROSS-TAB-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D3 | `CachePersistencePort` 는 `BlobStorePort` 를 재사용하지 않고 자체 백엔드를 가진다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | + + +### 선언한 예외 + +해당 없음. + + +### 가져온 artifact 계약 + +| Artifact Ref | Owner | Producer | Schema Ref | +|---|---|---|---| + + + +## 가져온 프로젝트 계약 + +| Ref | Owner | 요약 | Branch 적용 | +|---|---|---|---| +| `FE-GATE-028@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | version 파티션·탭 간 무효화 fixture 가 실패하면 merge 를 MUST 차단 | 이 branch 가 owner 로서 fixture 와 report 를 산출 | +| `FE-OC-012@1` | [[raw/branch-notes/feature-server-state-caching-contract]] | query key와 invalidation은 registry factory만 MUST 사용 | 탭 간 전파도 factory key 만 사용 | +| `FE-OC-013@1` | [[raw/branch-notes/feature-frontend-storage-registry-contract]] | storage key는 namespace·version·classification을 MUST 가지며 token/secret 저장을 금지 | `QUERY_CACHE_SNAPSHOT` 행을 소비 | +| `FE-OC-023@1` | [[raw/branch-notes/feature-frontend-contract-compatibility-governance]] | API/config/storage/release schema의 breaking change는 migration 또는 version bump 없이 배포하면 안 됨 | 파티션 키 불일치 시 폐기 규칙이 migration 대체 | + + + +### 수신한 위임 + +없음. 이 branch 는 `DELEG-FE-009` 의 delegator 다. + + + + +### 가져온 흐름 단계 + +| Stage Ref | Order | Owner | Input | Action | Output | +|---|---:|---|---|---|---| + + + +## 목표 + +`CachePersistencePort` 와 `CrossTabSyncPort` 를 정의하고, memory↔session↔local↔IndexedDB 캐시 계층과 탭 간 무효화를 고정한다. 이 계약이 없으면 탭 A 의 mutation 이 탭 B 의 캐시를 무효화하지 않아 두 탭이 서로 다른 사실을 보여주고, release 를 넘어 살아남은 영속 캐시가 새 스키마로 파싱되어 렌더 트리 깊은 곳에서 터진다. + +- 이슈: +- PR: + + +## 범위 + +### 포함 범위 + +- `CachePersistencePort` — 캐시 스냅샷 직렬화·영속·복원과 복원 거부 +- `CrossTabSyncPort` — BroadcastChannel 우선, `storage` event fallback, 채널 부재 시 탭 내 무효화만 +- `persistenceTier`·`crossTabScope` 규칙(`FE-REG-QUERY` 확장) 소비 +- release·config·API version 파티션과 불일치 시 폐기 +- `CAP_FE_CACHE_PERSISTENCE` capability 행 소유 + +### 제외 범위 + +- **use case, domain model, business rule** — port 와 adapter 계약까지만 정의한다 +- `QueryCachePort` 의 **정책**(stale time·gc·refetch·invalidation 매핑) — [[raw/branch-notes/feature-server-state-caching-contract]] 소유 +- physical storage key·namespace·classification·quota fallback — `DELEG-FE-009` 로 [[raw/branch-notes/feature-frontend-storage-registry-contract]] 에 위임 +- offline-first 동기화 충돌 해결(CRDT·last-write-wins 등) +- leader election 기반 단일 리더 동기화 — `FE-D028` 이 명시적으로 배제 + +## 근거 (필수, 최소 1개+) + +| Source | 정당화하는 결정 | +|---|---| +| `[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]]` §5.1·§6.2 | port 분해와 `FE-REG-QUERY` 확장 | +| [[raw/official-docs/tanstack-query-server-state-official]] | `QueryCachePort` 정책의 상위 근거 (persistence 는 이 문서가 다루지 않음) | +| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5.7·§9.2 | query key registry 와 cache defaults | + +**근거 등급 경계**: `FE-D028`(탭 간 무효화 transport)의 rationale 은 `project-local default, 외부 source claim 아님` 이다. TanStack Query 공식 문서는 persister 를 다루지만 이 repo 의 raw 발췌에는 그 내용이 없으며, BroadcastChannel·`storage` event 근거도 미수집이다(`FE-Q-011`). + +## TODO + +- [ ] `CachePersistencePort` 인터페이스와 파티션 키 규칙 확정 — 등급: `planned` +- [ ] `CrossTabSyncPort` 인터페이스와 메시지 봉투 확정 — 등급: `planned` +- [ ] version 불일치 캐시 폐기 fixture — 등급: `planned` +- [ ] 탭 A mutation → 탭 B 무효화 integration fixture — 등급: `planned` +- [ ] BroadcastChannel 부재 시 `storage` event fallback fixture — 등급: `planned` +- [ ] 두 transport 모두 불가 시 `CROSS_TAB_CHANNEL_UNAVAILABLE` 처리 — 등급: `planned` +- [ ] `FE-GATE-028` cache tier report 산출 — 등급: `planned` + +## 진행 중 메모 + +`crossTabScope: same-origin` 이 값이 아니라 key 만 전파하는 이유는 두 가지다. 값을 전파하면 (1) 수신 탭이 자기 권한으로 얻지 않은 데이터를 갖게 되고, (2) 메시지가 커져 `storage` event fallback 의 크기 제한에 부딪힌다. 수신 탭은 key 를 받아 자기 `QueryCachePort` 로 refetch 한다. + +## 결정 사항 + +- 2026-07-28: 파티션 키에 세 version 을 모두 포함 / 이유: 하나만 쓰면 config 만 바뀐 배포에서 stale 캐시가 살아남음 / 검토한 대안: `releaseId` 단독 / 근거: 근거 raw 미수집, project-local 판단 (`FE-Q-011`) +- 2026-07-28: leader election 미도입 / 이유: 탭 간 무효화에 리더가 필요 없고 리더 선출 자체가 새 실패 모드 / 검토한 대안: Web Locks 기반 리더 / 근거: `FE-D028` + + +## 결정-근거 매핑 + +| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | +|---|---|---|---|---|---| +| D1 | 세 version 파티션 + 불일치 시 폐기 | 항상. migration 이 폐기보다 싼 대용량 캐시가 생기면 재검토 | 없음 — `FE-Q-011` | `project-local default` | 매 배포마다 캐시가 비워져 첫 로드가 느려질 수 있음 | +| D2 | key 만 전파 | 항상. 값 전파가 필요한 실시간 협업 요구가 생기면 재검토 | 없음 — `FE-Q-011` | `project-local default` | 수신 탭의 refetch 가 몰려 backend 부하가 튈 수 있음 | +| D3 | `CachePersistencePort` 가 자체 백엔드 보유 | 항상. 두 port 가 같은 IndexedDB 를 두고 quota 경쟁하면 재검토 | 없음 — `FE-Q-011` | `project-local default` | 같은 origin 에서 두 개의 IndexedDB 사용처가 생김 | + + +## 구현 가이드 + +> 근거 raw 자료(`FE-Q-011`) 수집 전까지 비워 둔다. 지금 채우면 모든 cell 이 `UNSUPPORTED_IMPL_DECISION` 이 된다. + + +## 엣지·실패·의존 + +- **실패·엣지 경로** + - 직렬화·영속·복원 실패 → `CACHE_PERSISTENCE_FAILURE`, 메모리 캐시만 사용하고 제품 흐름을 막지 않음 + - BroadcastChannel 과 `storage` event 모두 불가 → `CROSS_TAB_CHANNEL_UNAVAILABLE`, 탭 내 무효화만 수행 + - 파티션 키 불일치 캐시 발견 → 복원하지 않고 폐기. 부분 복원 금지 + - 수신 탭이 무효화 key 를 받았으나 해당 query 를 구독하지 않음 → 무시 (에러 아님) + - 다중 탭이 동시에 같은 key 를 무효화 → 중복 refetch 를 `QueryCachePort` 의 dedup 이 흡수해야 함 +- **다른 계약 의존** + - [[raw/branch-notes/feature-server-state-caching-contract]] 의 `QueryCachePort` 정책에 의존 — invalidation 매핑이 바뀌면 전파 대상이 바뀜 + - [[raw/branch-notes/feature-frontend-storage-registry-contract]] 의 `QUERY_CACHE_SNAPSHOT` 행에 의존 (`DELEG-FE-009`) + - [[raw/branch-notes/feature-frontend-contract-compatibility-governance]] 의 version tuple 에 의존 — 파티션 키가 그 tuple 에서 나옴 + + +## 검증해야 할 주장 + +| Claim | Why uncertain | How to verify | Status | +|---|---|---|---| +| version 불일치 캐시가 복원되지 않는다 | 부분 복원이 조용히 성공하기 쉬움 | negative fixture — 이전 version 스냅샷 주입 후 복원 시도가 폐기로 끝나는지 | `planned` | +| 탭 A mutation 이 탭 B 캐시를 무효화한다 | BroadcastChannel 은 같은 origin 의 다른 탭에서만 동작 | integration test — 두 컨텍스트에서 발행/수신 확인 | `planned` | +| BroadcastChannel 부재 시 `storage` event 로 대체된다 | fallback 경로가 실제로 도달하는지 불확실 | fixture — BroadcastChannel 을 undefined 로 만들고 전파 확인 | `planned` | +| key 만 전파해도 UI 가 일관된다 | 수신 탭의 refetch 타이밍에 따라 잠깐 어긋날 수 있음 | integration test — 전파 후 두 탭의 최종 상태 일치 | `needs-confirmation` | +| 다중 탭 동시 무효화가 refetch 폭주를 만들지 않는다 | dedup 이 `QueryCachePort` 책임인지 이 branch 책임인지 경계가 얇음 | 부하 fixture — N개 탭 시뮬레이션 후 실제 요청 수 측정 | `needs-confirmation` | + +## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때) + +미생성. + +## 마주친 문제 + +없음. + +## 묶음 (이 branch에서 파생된 자료) + +### Sub-branches (세부 작업) + +아직 없음. + +### 오류 기록 (이 branch 작업 중 발생) + +아직 없음. + +### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) + +아직 없음. + +### 강의 (이 작업을 위해 학습한 강의) + +아직 없음. + +### job-posting tie-ins (이 작업에서 파생된 글감) + +아직 없음. + +## 관련 일일 노트 + +- 아직 없음 + +## 완료 후 정리 + +- PR 링크: +- 리뷰 메모: +- 머지 결과 / 배포 환경: +- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): + - `actually-implemented` 항목: 없음 + - `locally-verified` 항목: 없음 + - `prod-verified` 항목: 없음 +- **추출하지 않을 항목**: 현재 전 항목 `planned` diff --git a/raw/branch-notes/feature-frontend-large-object-transfer-contract.md b/raw/branch-notes/feature-frontend-large-object-transfer-contract.md new file mode 100644 index 0000000..bfa6938 --- /dev/null +++ b/raw/branch-notes/feature-frontend-large-object-transfer-contract.md @@ -0,0 +1,247 @@ +--- +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] +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 + + +## 부모 (필수) + +- [[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]] + + +## 브랜치 계약 패킷 + +- **생성 시 프로젝트 개정**: `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` | 근거 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` | + + +### 선언한 예외 + +해당 없음. + + +### 가져온 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 | +|---|---:|---|---|---|---| + + + +## 목표 + +`UploadTransferPort` 와 `StreamingDownloadPort` 를 정의하고, presigned URL 획득과 byte 전송 사이의 credential 경계를 고정한다. 이 계약이 없으면 presigned URL 로 나가는 요청에 session 헤더가 그대로 붙어 제3자 스토리지 도메인에 인증 정보가 전달된다. 재개 가능 전송의 part 상태 소유자도 함께 고정한다. + +- 이슈: +- PR: + + +## 범위 + +### 포함 범위 + +- `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 + + +## 결정-근거 매핑 + +| 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 을 만나면 설계가 바뀜 | + + +## 구현 가이드 + +> 근거 raw 자료(`FE-Q-011`, `FE-Q-012`) 수집 전까지 비워 둔다. 특히 part size·병렬도·만료 처리는 storage vendor 제약에 직접 의존하므로, vendor 확정 전에 쓰면 전부 `UNSUPPORTED_IMPL_DECISION` 이 된다. + + +## 엣지·실패·의존 + +- **실패·엣지 경로** + - 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 유출 차단 + + +## 검증해야 할 주장 + +| 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` 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 new file mode 100644 index 0000000..dd6ea61 --- /dev/null +++ b/raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract.md @@ -0,0 +1,248 @@ +--- +title: branch / feature-frontend-multi-protocol-api-transport-contract +source_type: branch-note +status: raw +id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-031 +kind: project-work-item +project: ca-skeleton-frontend-operational-contract +work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-031 +inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PROTOCOL-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-VALIDATION-001@1] +refines: [] +overrides: [] +depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-006, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-007] +imports: [FE-GATE-030@1, FE-OC-006@1, FE-OC-007@1, FE-OC-008@1, FLOW-FE-RESP-004@1, FLOW-FE-RESP-005@1, FLOW-FE-RESP-006@1] +delegates: [DELEG-FE-010] +accepts_delegations: [] +contract_packet: 1 +branch: feature-frontend-multi-protocol-api-transport-contract +parent_branch: +related_projects: [ca-skeleton-frontend, ca-skeleton] +tags: [branch, ca-skeleton-frontend, api, protocol, graphql, grpc-web] +created: 2026-07-28 +target_merge: +status_label: in-progress +--- + +# branch: feature-frontend-multi-protocol-api-transport-contract + + +## 부모 (필수) + +- [[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-large-object-transfer-contract]] +- [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]] +- [[raw/branch-notes/feature-frontend-background-execution-worker-contract]] + + +## 브랜치 계약 패킷 + +- **생성 시 프로젝트 개정**: `2` +- **패킷 스키마**: `contract_packet: 1` +- **완료 조건**: protocol별 성공/실패 정규화와 gateway fallback 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_ALT_PROTOCOL` 을 이 branch 가 소유하고 default OFF 로 유지한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PROTOCOL-001@1` | transport default는 REST이고 GraphQL·gRPC-Web·Connect-Web은 FE-REG-API의 protocol 필드로 opt-in하며 미지원 환경은 REST gateway로 fallback한다 | `FE-REG-API.protocol` 값에 따른 adapter 선택 규칙에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-VALIDATION-001@1` | boundary runtime validation은 Zod schema로 수행한다 | codec 디코드 결과도 예외 없이 스키마 검증을 거친다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | + + +### 브랜치 지역 결정 + +| Decision ID | Decision | Relation | Supporting Claims | Status | +|---|---|---|---|---| +| D1 | 이 branch 는 **신규 port 를 정의하지 않는다**. GraphQL·gRPC-Web·Connect-Web adapter 는 기존 `ResourceQueryPort`/`ResourceCommandPort` 를 구현한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PROTOCOL-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D2 | transport status 만으로 성공을 판정하지 않는다. protocol 별 성공 판정 함수가 별도로 존재한다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D3 | GraphQL `200 OK` + `errors[]` 는 부분 성공이 아니라 `PARTIAL_RESULT_FAILURE` 로 정규화한다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | + + +### 선언한 예외 + +해당 없음. + + +### 가져온 artifact 계약 + +| Artifact Ref | Owner | Producer | Schema Ref | +|---|---|---|---| + + + +## 가져온 프로젝트 계약 + +| Ref | Owner | 요약 | Branch 적용 | +|---|---|---|---| +| `FE-GATE-030@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | protocol 별 성공/실패 정규화 fixture 가 실패하면 merge 를 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에서 구현하면 안 됨 | protocol adapter 도 shared client 위에 얹힌다 | +| `FE-OC-007@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | JSON envelope와 payload는 boundary에서 runtime schema를 MUST 통과 | 디코드 후 검증을 `DELEG-FE-010` 으로 위임 | +| `FE-OC-008@1` | [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] | 모든 failure는 stable frontend error kind로 MUST 정규화하고 raw body·stack을 UI에 노출하면 안 됨 | protocol 실패 3종의 정규화 대상 | + + + +### 수신한 위임 + +없음. 이 branch 는 `DELEG-FE-010` 의 delegator 다. + + + + +### 가져온 흐름 단계 + +| Stage Ref | Order | Owner | Input | Action | Output | +|---|---:|---|---|---|---| +| `FLOW-FE-RESP-004@1` | 4 | `feature-runtime-schema-validation-contract` | unvalidated JSON | envelope 공유 스키마 검증 | discriminated envelope | +| `FLOW-FE-RESP-005@1` | 5 | `feature-runtime-schema-validation-contract` | discriminated envelope | success/failure 분기 검증 | 분기 확정 envelope | +| `FLOW-FE-RESP-006@1` | 6 | `feature-runtime-schema-validation-contract` | 분기 확정 envelope | payload per-operation 스키마 검증 | 검증된 payload(deep clone) | + + + +## 목표 + +GraphQL·gRPC-Web·Connect-Web adapter 가 기존 `ResourceQueryPort`/`ResourceCommandPort` 를 구현하도록 고정하고, protocol 별 성공/실패 판정을 정규화된 failure 로 매핑한다. 이 계약이 없으면 `200 OK` + `errors[]` 응답이 success 로 반환되어 빈 화면이 정상처럼 보이고, HTTP 200 + `grpc-status: 13` 이 성공으로 처리된다. + +- 이슈: +- PR: + + +## 범위 + +### 포함 범위 + +- `FE-REG-API` 의 `protocol`·`operationRef`·`transferMode` 소비와 adapter 선택 +- GraphQL codec — persisted-document ID 기반 요청, `errors[]` 처리 +- gRPC-Web·Connect-Web codec — protobuf 인코딩/디코딩, `grpc-status` ↔ 정규화 kind 매핑 +- REST gateway fallback (`disabledFallback: degraded-alternative`) +- 신규 실패 3종 정규화: `PROTOCOL_STATUS_MISMATCH`·`CODEC_DECODE_FAILURE`·`PARTIAL_RESULT_FAILURE` +- `CAP_FE_ALT_PROTOCOL` capability 행 소유 + +### 제외 범위 + +- **use case, domain model, business rule** — adapter 계약까지만 정의한다 +- **신규 port 정의** — 0개가 이 branch 의 설계 결론이다. 프로토콜이 application 에 새 인터페이스로 새면 `FE-D010` dependency inversion 이 무너진다 +- GraphQL 스키마 설계, protobuf 서비스·메시지 정의 +- 스키마 생성 파이프라인의 SSOT — `FE-Q-013` +- 디코드 이후 payload 의 runtime schema 검증 — `DELEG-FE-010` 로 [[raw/branch-notes/feature-runtime-schema-validation-contract]] 에 위임 +- 스트림 protocol(`sse`·`websocket`·`poll`) — [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]] 소유 + +## 근거 (필수, 최소 1개+) + +| Source | 정당화하는 결정 | +|---|---| +| `[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]]` §5.2·§6.2 | 신규 port 0개 결론과 `FE-REG-API` 확장 | +| [[raw/official-docs/zod-runtime-schema-validation-official]] | 디코드 후 검증의 상위 근거 | +| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §2.1.4·§7.3·§8.2 | 응답 흐름 8단계와 실패 정규화 | + +**근거 등급 경계**: `FE-D031`(protocol opt-in)의 rationale 은 `project-local default, 외부 source claim 아님` 이다. gRPC-Web·Connect 프로토콜 명세와 GraphQL over HTTP 규약을 다룬 raw 자료는 이 repo 에 없다(`FE-Q-011`). 특히 `grpc-status` ↔ 정규화 kind 매핑표는 명세 확인 없이 확정할 수 없으므로 §구현 가이드를 비워 둔다. + +## TODO + +- [ ] `FE-REG-API.protocol` 별 adapter 선택 규칙 확정 — 등급: `planned` +- [ ] protocol 별 성공 판정 함수 분리 — 등급: `planned` +- [ ] GraphQL `errors[]` → `PARTIAL_RESULT_FAILURE` 매핑 — 등급: `planned` +- [ ] `grpc-status` → 정규화 kind 매핑표 (명세 확인 후) — 등급: `planned` +- [ ] codec decode 실패 → `CODEC_DECODE_FAILURE` — 등급: `planned` +- [ ] REST gateway fallback 경로 — 등급: `planned` +- [ ] `FE-GATE-030` protocol mapping report 산출 — 등급: `planned` + +## 진행 중 메모 + +이 branch 의 가치는 "무엇을 추가했는가" 보다 "무엇을 추가하지 않았는가" 에 있다. 신규 port 0개라는 결론이 유지되어야 backend 가 REST 에서 gRPC 로 옮겨갈 때 use case 를 다시 쓰지 않는다. 리뷰 시 port 가 늘어나 있으면 그 자체가 회귀 신호다. + +## 결정 사항 + +- 2026-07-28: 신규 port 0개 / 이유: 프로토콜은 registry 데이터이지 타입이 아니며, port 로 새면 dependency inversion 이 무너짐 / 검토한 대안: `GraphQLPort`·`GrpcWebPort` 분리 / 근거: 설계문서 §5.2 +- 2026-07-28: `200 + errors[]` 를 실패로 정규화 / 이유: 부분 데이터를 성공으로 취급하면 빈 화면이 정상처럼 보임 / 검토한 대안: 부분 성공 상태 신설 / 근거: 근거 raw 미수집, project-local 판단 (`FE-Q-011`) + + +## 결정-근거 매핑 + +| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | +|---|---|---|---|---|---| +| D1 | 신규 port 0개 | 항상. 프로토콜 고유 기능(gRPC 양방향 스트림 등)이 use case 레벨에 필요해지면 재검토 | 없음 — `FE-Q-011` | `project decision` | 추상화가 새는 프로토콜 기능이 있을 수 있음 | +| D2 | protocol 별 성공 판정 함수 분리 | 항상 | 없음 — `FE-Q-011` | `project-local default` | 판정 로직이 프로토콜마다 흩어져 중복될 수 있음 | +| D3 | `200 + errors[]` → `PARTIAL_RESULT_FAILURE` | 항상. 제품이 부분 데이터를 의미 있게 쓸 수 있으면 재검토 | 없음 — `FE-Q-011` | `project-local default` | 일부 필드만 실패한 응답을 통째로 버리게 됨 | + + +## 구현 가이드 + +> 근거 raw 자료(`FE-Q-011`, `FE-Q-013`) 수집 전까지 비워 둔다. `grpc-status` 코드별 매핑과 Connect 의 error 표현은 명세를 읽지 않고 쓸 수 없으며, 추측으로 쓰면 전부 `UNSUPPORTED_IMPL_DECISION` 이다. + + +## 엣지·실패·의존 + +- **실패·엣지 경로** + - HTTP 200 + `grpc-status` 비0 → `PROTOCOL_STATUS_MISMATCH`. protocol status 가 재시도 가능일 때만 safe/keyed 재시도 + - protobuf/GraphQL 디코드 실패 → `CODEC_DECODE_FAILURE`, 본문을 telemetry 에 남기지 않음 + - GraphQL `200 OK` + `errors[]` → `PARTIAL_RESULT_FAILURE`, error path count 만 telemetry + - capability OFF 또는 브라우저 미지원 → REST gateway 로 fallback (`degraded-alternative`) + - gateway 도 없으면 `CAPABILITY_UNSUPPORTED` +- **다른 계약 의존** + - [[raw/branch-notes/feature-api-client-response-envelope-contract]] 의 timeout·retry·idempotency 에 의존 + - [[raw/branch-notes/feature-runtime-schema-validation-contract]] 의 `FLOW-FE-RESP-004`~`006` 에 의존 (`DELEG-FE-010`) + - [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 총함수 정규화에 의존 — 신규 3종 kind 가 매핑되어야 함 + - [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 의 DTO→model 매핑에 의존 — 디코드 산출물이 mapper 입력 + + +## 검증해야 할 주장 + +| Claim | Why uncertain | How to verify | Status | +|---|---|---|---| +| `200 OK` + `errors[]` 가 success 로 반환되지 않는다 | GraphQL 클라이언트 기본 동작이 부분 성공 | negative fixture — 해당 응답 주입 후 `PARTIAL_RESULT_FAILURE` 확인 | `planned` | +| HTTP 200 + `grpc-status: 13` 이 실패로 정규화된다 | transport status 만 보는 구현이 흔함 | negative fixture — trailer 주입 후 kind 확인 | `planned` | +| protocol adapter 가 신규 port 를 만들지 않았다 | 구현 중 편의로 port 가 늘어나기 쉬움 | architecture fixture — `application/ports/` 파일 수가 늘지 않았는지 | `planned` | +| REST gateway fallback 이 실제로 도달한다 | capability OFF 경로가 테스트에서 빠지기 쉬움 | integration test — flag OFF 로 같은 operation 호출 | `planned` | +| codec 산출물이 반드시 스키마 검증을 거친다 | 디코드가 이미 타입을 보장한다고 착각하기 쉬움 | negative fixture — 스키마 위반 디코드 결과 주입 후 `SCHEMA_MISMATCH` | `planned` | +| `grpc-status` 매핑표가 명세와 일치한다 | 명세 미확인 상태 | `FE-Q-011` 수집 후 명세 대조 | `needs-confirmation` | + +## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때) + +미생성. + +## 마주친 문제 + +없음. + +## 묶음 (이 branch에서 파생된 자료) + +### Sub-branches (세부 작업) + +아직 없음. + +### 오류 기록 (이 branch 작업 중 발생) + +아직 없음. + +### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) + +아직 없음. + +### 강의 (이 작업을 위해 학습한 강의) + +아직 없음. + +### job-posting tie-ins (이 작업에서 파생된 글감) + +아직 없음. + +## 관련 일일 노트 + +- 아직 없음 + +## 완료 후 정리 + +- PR 링크: +- 리뷰 메모: +- 머지 결과 / 배포 환경: +- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): + - `actually-implemented` 항목: 없음 + - `locally-verified` 항목: 없음 + - `prod-verified` 항목: 없음 +- **추출하지 않을 항목**: 현재 전 항목 `planned` diff --git a/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md b/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md new file mode 100644 index 0000000..a79c0db --- /dev/null +++ b/raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract.md @@ -0,0 +1,266 @@ +--- +title: branch / feature-frontend-realtime-subscription-lifecycle-contract +source_type: branch-note +status: raw +id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-032 +kind: project-work-item +project: ca-skeleton-frontend-operational-contract +work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-032 +inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REALTIME-TRANSPORT-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REALTIME-LIFECYCLE-001@1] +refines: [] +overrides: [] +depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-006, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-007, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-013] +imports: [FE-GATE-031@1, FE-OC-007@1, FE-OC-008@1, FE-OC-011@1, FE-OC-025@1, FLOW-FE-EVENT-003@1, FLOW-FE-EVENT-004@1, FLOW-FE-EVENT-005@1] +delegates: [DELEG-FE-007] +accepts_delegations: [] +contract_packet: 1 +branch: feature-frontend-realtime-subscription-lifecycle-contract +parent_branch: +related_projects: [ca-skeleton-frontend, ca-skeleton] +tags: [branch, ca-skeleton-frontend, realtime, sse, websocket, push] +created: 2026-07-28 +target_merge: +status_label: in-progress +--- + +# branch: feature-frontend-realtime-subscription-lifecycle-contract + + +## 부모 (필수) + +- [[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-large-object-transfer-contract]] +- [[raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract]] +- [[raw/branch-notes/feature-frontend-background-execution-worker-contract]] + + +## 브랜치 계약 패킷 + +- **생성 시 프로젝트 개정**: `2` +- **패킷 스키마**: `contract_packet: 1` +- **완료 조건**: backoff·resume·구독 해제·이벤트 검증 fixture와 `FE-RB-006` drill이 통과한다 + + +### 상속한 프로젝트 결정 + +| 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_REALTIME` 을 이 branch 가 소유하고 default OFF 로 유지한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REALTIME-TRANSPORT-001@1` | 실시간 transport는 SSE를 우선하고 양방향이 필요하면 WebSocket, 둘 다 불가할 때만 최소 간격·backoff·visibility gating을 갖춘 bounded polling을 쓴다 | `RealtimeSubscriptionPort` adapter 선택 순서에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | +| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REALTIME-LIFECYCLE-001@1` | 실시간 연결은 full jitter backoff와 30초 cap을 쓰고 재시도 상한 후 terminal 상태로 전이하며 resume은 Last-Event-ID 또는 cursor로 수행하고 unmount 시 구독을 해제한다 | 연결 수명주기 상태 기계와 해제 계약에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | + + +### 브랜치 지역 결정 + +| Decision ID | Decision | Relation | Supporting Claims | Status | +|---|---|---|---|---| +| D1 | 구독 해제는 `RealtimeSubscriptionPort` 가 반환하는 handle 로만 수행하고, adapter 내부에 열린 연결 목록을 유지해 파괴 시 전부 닫는다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-REALTIME-LIFECYCLE-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | +| D2 | backoff jitter 의 random source 는 주입 가능해야 한다 (결정론 테스트 요건) | `local` | `DELEG-FE-005` 선례 — `feature-api-client-response-envelope-contract` 가 같은 문제를 이미 위임함 | `proposed` | +| D3 | 구독별로 독립적인 full jitter 를 쓰고, 추가로 전역 동시 재연결 상한을 둔다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` | + + +### 선언한 예외 + +해당 없음. + + +### 가져온 artifact 계약 + +| Artifact Ref | Owner | Producer | Schema Ref | +|---|---|---|---| + + + +## 가져온 프로젝트 계약 + +| Ref | Owner | 요약 | Branch 적용 | +|---|---|---|---| +| `FE-GATE-031@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | backoff·resume·구독 해제·이벤트 검증 fixture 가 실패하면 merge·release 를 MUST 차단 | 이 branch 가 owner 로서 fixture 와 report 를 산출 | +| `FE-OC-007@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | JSON envelope와 payload는 boundary에서 runtime schema를 MUST 통과 | 인바운드 프레임도 같은 규칙 적용 | +| `FE-OC-008@1` | [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] | 모든 failure는 stable frontend error kind로 MUST 정규화하고 raw body·stack을 UI에 노출하면 안 됨 | 실시간 실패 4종의 정규화 대상 | +| `FE-OC-011@1` | [[raw/branch-notes/feature-async-ui-state-contract]] | async surface는 initial-loading, success, empty, terminal-error를 MUST 표현 | §9.5 실시간 state 를 이 4-state 에 매핑 | +| `FE-OC-025@1` | [[raw/branch-notes/feature-frontend-operational-runbook-contract]] | boot, chunk mismatch, API degradation, telemetry failure, rollback, realtime 연결, background 실행 runbook을 MUST 유지 | `FE-RB-006` 의 기술 escalation 대상 | + + + +### 수신한 위임 + +없음. 이 branch 는 `DELEG-FE-007` 의 delegator 다. + + + + +### 가져온 흐름 단계 + +> `FLOW-FE-EVENT-001`·`002` 는 이 branch 가 **소유**하므로 import 하지 않는다. + +| Stage Ref | Order | Owner | Input | Action | Output | +|---|---:|---|---|---|---| +| `FLOW-FE-EVENT-003@1` | 3 | `feature-runtime-schema-validation-contract` | unvalidated event JSON | event envelope 공유 스키마 검증 | discriminated event envelope | +| `FLOW-FE-EVENT-004@1` | 4 | `feature-runtime-schema-validation-contract` | discriminated event envelope | `eventSchema` per-event 검증 | 검증된 event payload | +| `FLOW-FE-EVENT-005@1` | 5 | `feature-frontend-error-classification-boundary-contract` | 검증된 event payload 또는 실패 신호 | 정규화된 이벤트 또는 failure 반환 | application event 또는 normalized failure | + + + +## 목표 + +`RealtimeSubscriptionPort` 와 `PushSubscriptionPort` 를 정의하고, 연결·재연결·재개·이벤트 검증·해제를 계약한다. 이 계약이 없으면 끊김에 상한 없는 즉시 재연결을 걸어 backend 를 증폭 공격하고, 라우트 이탈 후에도 WebSocket 이 열린 채 남아 연결 수가 단조 증가하며, 미검증 프레임이 상태에 병합되어 `TypeError` 가 렌더 트리 깊은 곳에서 늦게 터진다. + +- 이슈: +- PR: + + +## 범위 + +### 포함 범위 + +- `RealtimeSubscriptionPort` — SSE·WebSocket·bounded polling adapter +- `PushSubscriptionPort` — 권한 요청, VAPID 공개키 기반 구독 등록 +- 연결 수명주기 상태 기계와 §9.5 surface state +- full jitter backoff, 30초 cap(`REALTIME_RECONNECT_CAP_MS`), 재시도 상한 후 terminal 전이 +- resume — `Last-Event-ID` / cursor, gap 감지 +- `FLOW-FE-EVENT-001`·`002` 소유 (프레임 수신, transport decode) +- 구독 해제 계약과 누수 검출(`SUBSCRIPTION_LEAKED` fixture) +- bounded polling 의 최소 간격·backoff·visibility gating +- `FE-RB-006` 의 기술 escalation +- `CAP_FE_REALTIME` capability 행 소유 + +### 제외 범위 + +- **use case, domain model, business rule** — port 와 adapter 계약까지만 정의한다 +- 이벤트의 도메인 의미와 상태 병합 정책 +- service worker 등록·수명주기 — `DELEG-FE-007` 로 [[raw/branch-notes/feature-frontend-background-execution-worker-contract]] 에 위임 (WebPush 는 SW 위에서 동작) +- push 발송 서버와 VAPID 개인키 관리 — `FE-Q-014` +- 인바운드 프레임의 스키마 **검증 구현** — `FLOW-FE-EVENT-003`·`004` 는 [[raw/branch-notes/feature-runtime-schema-validation-contract]] 소유 +- unary protocol adapter — [[raw/branch-notes/feature-frontend-multi-protocol-api-transport-contract]] 소유 + +## 근거 (필수, 최소 1개+) + +| Source | 정당화하는 결정 | +|---|---| +| `[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]]` §5.1·§7.5·§9.2 | port 분해, 이벤트 흐름 5단계, 실시간 surface state | +| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.5·§9.1 | 기존 retry backoff·jitter 정책과 async state 모델 | + +**근거 등급 경계**: `FE-D032`(transport 선택 순서)와 `FE-D033`(연결 수명주기)의 rationale 은 각각 `project-local default, 외부 source claim 아님` 과 `재연결 폭주·구독 누수 억제, project decision` 이다. SSE(WHATWG HTML `EventSource`)·WebSocket·Web Push 명세를 다룬 raw 자료는 이 repo 에 없다(`FE-Q-011`). 30초 cap 은 측정값이 아니라 초기 default 다. + +## TODO + +- [ ] `RealtimeSubscriptionPort` 인터페이스와 해제 handle 확정 — 등급: `planned` +- [ ] `PushSubscriptionPort` 인터페이스 확정 — 등급: `planned` +- [ ] transport 선택 순서(SSE → WebSocket → polling) 구현 — 등급: `planned` +- [ ] full jitter backoff + cap + terminal 전이 (주입 가능 random source) — 등급: `planned` +- [ ] resume cursor 와 gap 감지 — 등급: `planned` +- [ ] `FLOW-FE-EVENT-001`·`002` 프레임 수신·디코드 — 등급: `planned` +- [ ] 구독 해제 계약과 누수 fixture — 등급: `planned` +- [ ] bounded polling 의 visibility gating — 등급: `planned` +- [ ] §9.5 surface state 컴포넌트 매핑 — 등급: `planned` +- [ ] `FE-RB-006` drill assertion — 등급: `planned` +- [ ] `FE-GATE-031` realtime lifecycle report 산출 — 등급: `planned` + +## 진행 중 메모 + +`reconnecting` 과 `disconnected` 의 구분이 이 branch 의 핵심 UX 계약이다. 전자를 `terminal-error` 로 표시하면 사용자가 불필요하게 새로고침하고, 후자를 `refreshing` 으로 표시하면 영원히 오지 않는 데이터를 기다린다. 구현 시 두 상태가 같은 컴포넌트 분기로 합쳐지지 않는지 리뷰에서 확인한다. + +`D2`(주입 가능 random source)는 `DELEG-FE-005` 가 이미 같은 문제를 다뤘다 — `feature-api-client-response-envelope-contract` 가 `feature-frontend-clean-architecture-layering-contract` 에 주입 형태를 위임했다. 이 branch 는 그 결과를 재사용하고 새 위임을 만들지 않는다. + +## 결정 사항 + +- 2026-07-28: 재시도 상한 후 terminal 전이 / 이유: 무한 재시도는 backend 증폭이고 사용자에게도 상태를 숨김 / 검토한 대안: 무한 재시도 + 긴 cap / 근거: `FE-D033` +- 2026-07-28: 구독별 독립 jitter + 전역 동시 재연결 상한 / 이유: 구독이 여럿이면 같은 시점에 만료해 thundering herd 가 됨 / 검토한 대안: 전역 단일 backoff / 근거: `FE-RISK-016` + + +## 결정-근거 매핑 + +| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | +|---|---|---|---|---|---| +| D1 | handle 기반 해제 + adapter 내부 연결 목록 | 항상 | 없음 — `FE-Q-011` | `project-local default` | adapter 파괴 시점이 명확하지 않은 사용 패턴이 있을 수 있음 | +| D2 | 주입 가능 random source | 항상. 결정론 테스트 요건 | `DELEG-FE-005` 선례 | `project precedent` | 주입 형태가 layering branch 결정에 종속 | +| D3 | 구독별 독립 jitter + 전역 상한 | 구독이 2개 이상일 때. 단일 구독이면 전역 상한은 무의미 | 없음 — `FE-Q-011` | `project-local default` | 전역 상한이 정상 재연결을 지연시킬 수 있음 | + + +## 구현 가이드 + +> 근거 raw 자료(`FE-Q-011`) 수집 전까지 비워 둔다. SSE 의 재연결·`Last-Event-ID` 동작과 WebSocket close code 해석은 명세를 읽지 않고 쓸 수 없다. + + +## 엣지·실패·의존 + +- **실패·엣지 경로** + - 최초 연결 실패 → `REALTIME_CONNECT_FAILED`, backoff 재시도, `connecting` 유지 + - 재시도 상한 소진 → `REALTIME_DISCONNECTED` terminal. **자동 재시도 없음**, 사용자 주도 retry + - resume cursor 로 메울 수 없는 공백 → `REALTIME_RESUME_GAP`, 권위 데이터 refetch 권고 + - 프레임 스키마 위반 → `EVENT_SCHEMA_MISMATCH`, 해당 프레임만 드롭하고 **연결은 유지** + - transport decode 실패 → 프레임을 버리되 연결을 즉시 끊지 않음(`FLOW-FE-EVENT-002` 불변식) + - unmount → 구독 해제. 해제 누락은 `SUBSCRIPTION_LEAKED` fixture 가 잡는 결함이지 런타임 kind 가 아님 + - 알림 권한 거부 → `PUSH_PERMISSION_DENIED`, 재요청 반복 금지 + - SSE·WebSocket 모두 미지원 → bounded polling 으로 강등(`degraded-alternative`) +- **다른 계약 의존** + - [[raw/branch-notes/feature-runtime-schema-validation-contract]] 의 `FLOW-FE-EVENT-003`·`004` 에 의존 + - [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 `FLOW-FE-EVENT-005` 총함수에 의존 + - [[raw/branch-notes/feature-async-ui-state-contract]] 의 4-state 모델에 의존 — §9.5 가 그 위에 매핑됨 + - [[raw/branch-notes/feature-frontend-background-execution-worker-contract]] 의 SW 등록에 의존 (`DELEG-FE-007`, WebPush 전용) + - [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] 의 주입 형태 결정에 의존 (`DELEG-FE-005` 결과 재사용) + + +## 검증해야 할 주장 + +| Claim | Why uncertain | How to verify | Status | +|---|---|---|---| +| unmount 후 열린 구독이 0이다 | 해제 누락이 조용히 누적됨 | fixture — 컴포넌트 mount/unmount 반복 후 adapter 내부 연결 목록 길이 0 (`FE-NFR-017`) | `planned` | +| 재연결 간격이 30초 cap 을 넘지 않는다 | jitter 계산이 cap 을 초과하기 쉬움 | 결정론 fake clock test — 모든 시도 간격 ≤ cap (`FE-NFR-016`) | `planned` | +| 상한 소진 후 자동 재시도가 멈춘다 | 무한 루프가 조용히 남음 | fixture — 상한 도달 후 추가 연결 시도 0 | `planned` | +| 미검증 프레임이 application 에 도달하지 않는다 | 검증을 우회하는 빠른 경로가 생기기 쉬움 | negative fixture — 스키마 위반 프레임 주입 후 application 콜백 미호출 | `planned` | +| 스키마 위반 프레임 하나가 연결을 끊지 않는다 | 방어적으로 끊는 구현이 흔함 | fixture — 위반 프레임 후 정상 프레임 수신 확인 | `planned` | +| resume gap 이 실제로 감지된다 | 서버가 gap 을 알려주지 않으면 감지 방법이 제한적 | integration test — 이벤트 ID 불연속 주입 후 `REALTIME_RESUME_GAP` | `needs-confirmation` | +| 다중 구독 재연결이 동시에 몰리지 않는다 | 독립 jitter 만으로 충분한지 불확실 | 부하 fixture — N개 구독 동시 끊김 후 재연결 시각 분포 (`FE-RISK-016`) | `needs-confirmation` | +| bounded polling 이 백그라운드 탭에서 멈춘다 | visibility gating 누락이 흔함 | fixture — `document.hidden` 상태에서 요청 0 | `planned` | + +## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때) + +미생성. + +## 마주친 문제 + +없음. + +## 묶음 (이 branch에서 파생된 자료) + +### Sub-branches (세부 작업) + +아직 없음. + +### 오류 기록 (이 branch 작업 중 발생) + +아직 없음. + +### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) + +아직 없음. + +### 강의 (이 작업을 위해 학습한 강의) + +아직 없음. + +### job-posting tie-ins (이 작업에서 파생된 글감) + +아직 없음. + +## 관련 일일 노트 + +- 아직 없음 + +## 완료 후 정리 + +- PR 링크: +- 리뷰 메모: +- 머지 결과 / 배포 환경: +- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): + - `actually-implemented` 항목: 없음 + - `locally-verified` 항목: 없음 + - `prod-verified` 항목: 없음 +- **추출하지 않을 항목**: 현재 전 항목 `planned`