Files
llm-wiki/raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract.md

33 KiB

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, imports, delegates, accepts_delegations, contract_packet, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label
title source_type status id kind project work_item inherits refines overrides depends_on imports delegates accepts_delegations contract_packet branch parent_branch related_projects governing_docs tags created target_merge status_label
branch / feature-frontend-cache-tier-cross-tab-invalidation-contract branch-note raw BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-029 project-work-item ca-skeleton-frontend-operational-contract WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-029
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CROSS-TAB-001@2
DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-SERVER-STATE-001@1
WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-010
WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-011
FE-GATE-028@1
FE-OC-012@1
FE-OC-013@1
FE-OC-023@1
DELEG-FE-009@1
1 feature-frontend-cache-tier-cross-tab-invalidation-contract
ca-skeleton-frontend
ca-skeleton
raw/project-notes/ca-skeleton-frontend-operational-contract.md
branch
ca-skeleton-frontend
cache
server-state
cross-tab
2026-07-28 in-progress

branch: feature-frontend-cache-tier-cross-tab-invalidation-contract

부모 (필수)

형제 branch (같은 부모, 이번 확장에서 함께 생성):

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 2
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: version 파티션·탭 간 무효화·채널 부재 시 탭 내 무효화 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@2 탭 간 무효화는 BroadcastChannel만 쓰고 별도 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/official-docs/tanstack-query-persistence-hydration-official.md#C1, #C2 (폐기 동작). tuple 구성은 project-local proposed
D2 탭 간 메시지는 무효화 key 만 싣고 값을 싣지 않는다 refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CROSS-TAB-001@2 raw/official-docs/mdn-broadcastchannel-storage-event.md#C5 (structured clone 이라 값 전송은 가능 — 금지는 우리 선택) proposed
D3 CachePersistencePortBlobStorePort 를 재사용하지 않고 자체 백엔드를 가진다 UNSUPPORTED_DECISION — 조사 후에도 근거 없음. 오히려 raw/official-docs/mdn-storage-quotas-eviction-persistence.md#C1·#C2 는 백엔드를 나눠도 quota·eviction 은 origin 단위로 함께 움직인다고 말하므로 "quota 격리" 를 이 결정의 근거로 쓸 수 없다. trade-off: 그럼에도 분리를 택한 이유는 FE-OC-002 의 port 경계다 — 캐시 스냅샷과 전송 버퍼는 수명·폐기 규칙·소유 branch 가 전부 다르고, 하나의 port 로 묶으면 한쪽 폐기 규칙이 다른 쪽에 샌다. 비용은 같은 origin 에 IndexedDB 사용처가 둘로 늘어나는 것이며 이는 quota 경쟁으로 나타난다 proposed
D4 로컬 무효화는 채널을 거치지 않고 직접 수행한다. adapter 는 컨텍스트당 BroadcastChannel 객체를 정확히 1개 유지한다 local raw/official-docs/mdn-broadcastchannel-storage-event.md#C2 (보낸 객체만 제외) proposed
D5 채널을 만들 수 없으면 대체 경로를 만들지 않고 탭 내 무효화만 수행한다 refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CROSS-TAB-001@2 raw/official-docs/mdn-broadcastchannel-storage-event.md#C4 (2022-03 부터 모든 주요 브라우저에서 동작), #C10 (대체 후보였던 sessionStorage 경로는 다른 탭에 도달하지 않음) proposed
D6 복원은 bootstrap 에서 await 하여 첫 렌더 이전에 끝낸다. 복원 실패·불일치는 부팅을 막지 않고 메모리 캐시로 진행한다 local raw/official-docs/tanstack-query-persistence-hydration-official.md#C5 (복원 중 렌더는 mount·fetch 와 경합) proposed
D7 영속 대상은 성공한 queryFE-REG-QUERY.persistenceTiermemory 가 아닌 행으로 한정하고, 직렬화는 adapter 책임이다 local raw/official-docs/tanstack-query-persistence-hydration-official.md#C6 (기본이 성공 query 만), #C7 (직렬화는 소비자 책임) proposed

deferred (이번 회차 조사 범위 밖): D8 — 영속 write 의 최소 간격과 병합 정책. tanstack-query-persistence-hydration-official.md#C4번들 persister 가 1초 throttle 을 쓴다고만 말하므로 custom adapter 값의 근거가 아니다. 적정 간격은 스냅샷 크기 실측 후 정한다.

선언한 예외

해당 없음.

가져온 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

목표

CachePersistencePortCrossTabSyncPort 를 정의하고, memory↔session↔local↔IndexedDB 캐시 계층과 탭 간 무효화를 고정한다. 이 계약이 없으면 탭 A 의 mutation 이 탭 B 의 캐시를 무효화하지 않아 두 탭이 서로 다른 사실을 보여주고, release 를 넘어 살아남은 영속 캐시가 새 스키마로 파싱되어 렌더 트리 깊은 곳에서 터진다.

  • 이슈:
  • PR:

범위

포함 범위

  • CachePersistencePort — 캐시 스냅샷 직렬화·영속·복원과 복원 거부
  • CrossTabSyncPort — BroadcastChannel 전송, 채널 부재 시 탭 내 무효화만
  • persistenceTier·crossTabScope 규칙(FE-REG-QUERY 확장) 소비
  • release·config·API version 파티션과 불일치 시 폐기
  • CAP_FE_CACHE_PERSISTENCE capability 행 소유

제외 범위

근거 (필수, 최소 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/official-docs/tanstack-query-persistence-hydration-official D1 불일치 캐시 전량 폐기 · D6 복원/렌더 경합 gating · D7 영속 대상 선별과 직렬화 책임
raw/official-docs/mdn-broadcastchannel-storage-event D2 메시지 봉투 제약 · D4 발신자 자기 수신 불가 · D5 fallback 이 localStorage 여야 하는 이유
raw/official-docs/mdn-storage-quotas-eviction-persistence D2 fallback 크기 예산(#C6) · D3 반증(#C1·#C2 — 백엔드를 나눠도 quota 는 분리되지 않음)
raw/project-notes/ca-skeleton-frontend-operational-contract §5.7·§9.2 query key registry 와 cache defaults

근거 등급 경계: 2026-07-28 /branch-spec 조사로 채널의 의미론(전달 범위·자기 제외·직렬화)과 캐시 영속의 폐기·복원 동작은 공식 문서 근거를 확보했다. 아직 근거가 없는 것은 파티션 키를 세 값으로 구성한다는 D1 의 tuple 구성이다 — 라이브러리는 buster 가 문자열이라는 것만 말한다(tanstack-query-persistence-hydration-official#C1). §검증해야 할 주장에 기록했다. 조사 중 나왔던 "대체 경로의 존재 이유 부족" 은 경로 자체를 삭제해 해소했다(§Audit & Findings).

TODO

  • CachePersistencePort 인터페이스와 파티션 키 규칙 확정 — 등급: planned
  • CrossTabSyncPort 인터페이스와 메시지 봉투 확정 — 등급: planned
  • version 불일치 캐시 폐기 fixture — 등급: planned
  • 탭 A mutation → 탭 B 무효화 integration fixture — 등급: planned
  • BroadcastChannel 부재 시 CROSS_TAB_CHANNEL_UNAVAILABLE + 탭 내 무효화만 (D5) — 등급: planned
  • 발신 탭 자기 수신 없음 negative fixture (D4) — 등급: planned
  • 복원 await 가 첫 렌더보다 앞서는지 순서 fixture (D6) — 등급: planned
  • persistenceTier 미지정 query 가 영속되지 않음을 확인하는 fixture (D7) — 등급: planned
  • 파티션 폐기·fallback 진입·채널 부재 telemetry event 등록 (관심사 커버리지 should-fix) — 등급: planned
  • FE-GATE-028 cache tier report 산출 — 등급: planned

진행 중 메모

crossTabScope: same-origin 이 값이 아니라 key 만 전파하는 이유는 하나다. 값을 전파하면 수신 탭이 자기 권한으로 얻지 않은 데이터를 갖게 된다. 수신 탭은 key 를 받아 자기 QueryCachePort 로 refetch 한다.

이건 플랫폼 제약이 아니다. mdn-broadcastchannel-storage-event#C5 대로 structured clone 이라 값을 그대로 보낼 수 있다 — 안 보내는 건 우리 정책이다.

결정 사항

  • 2026-07-28: 파티션 키에 세 version 을 모두 포함 / 이유: 하나만 쓰면 config 만 바뀐 배포에서 stale 캐시가 살아남음 / 검토한 대안: releaseId 단독 / 근거: 근거 raw 미수집, project-local 판단 (FE-Q-011)
  • 2026-07-28: leader election 미도입 / 이유: 탭 간 무효화에 리더가 필요 없고 리더 선출 자체가 새 실패 모드 / 검토한 대안: Web Locks 기반 리더 / 근거: FE-D028
  • 2026-07-28: storage event 대체 경로 삭제 / 이유: BroadcastChannel 이 2022-03 부터 모든 주요 브라우저에서 동작하고, 대체가 필요한 환경이 지원 대상에 있다는 근거가 없다. 평소 실행되지 않는 경로는 테스트로도 검증되지 않으면서 저장 예산·자기 수신 제외·연속 동일값 회피 장치를 계속 요구한다 / 검토한 대안: localStorage signal key 유지 / 근거: mdn-broadcastchannel-storage-event#C4·#C10, hub CROSS-TAB-001 revision 2

결정-근거 매핑

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 세 version 파티션 + 불일치 시 전량 폐기 항상. migration 이 폐기보다 싼 대용량 캐시가 생기면 재검토 tanstack-query-persistence-hydration-official.md#C1(buster 불일치 → discarded), #C2(removeClient() 후 즉시 폐기, 부분 복원 경로 없음) official-reference(폐기 동작) + project-local default(tuple 구성) 매 배포마다 캐시가 비워져 첫 로드가 느려진다. 세 값 중 어느 것이 실제로 캐시를 무효화해야 하는지는 미측정
D2 key 만 전파 항상. 값 전파가 필요한 실시간 협업 요구가 생기면 재검토 mdn-broadcastchannel-storage-event.md#C5(structured clone — 값 전송은 기술적으로 가능), mdn-storage-quotas-eviction-persistence.md#C6(localStorage 5 MiB) project-local default(금지 자체) + official-reference(제약) 수신 탭의 refetch 가 몰려 backend 부하가 튄다. dedup 경계가 QueryCachePort 와 얇다
D3 CachePersistencePort 가 자체 백엔드 보유 항상. 단, quota 격리를 근거로 쓰면 안 된다 없음 — 조사 결과 반증만 나왔다(mdn-storage-quotas-eviction-persistence.md#C1·#C2: origin 단위 전량 eviction) UNSUPPORTED_DECISION 같은 origin 에 IndexedDB 사용처가 둘. 한쪽이 quota 를 소진하면 다른 쪽도 함께 브라우저 eviction 대상이 된다
D4 로컬 무효화는 채널 왕복 없이 직접 수행 항상. 발신자 echo 가 없으므로 예외 없음 mdn-broadcastchannel-storage-event.md#C2 official-reference 채널 객체를 실수로 2개 만들면 자기 메시지를 자기가 받는 경로가 생겨 무효화가 2회 실행된다
D5 대체 경로 없음 — 채널 부재 시 탭 내 무효화만 항상. 지원 대상 브라우저에 BroadcastChannel 미동작 환경이 실제로 들어오면 재검토 mdn-broadcastchannel-storage-event.md#C4, #C10 official-reference 미동작 환경이 나중에 발견되면 그 환경의 사용자는 다중 탭에서 stale 을 본다. 감지는 CROSS_TAB_CHANNEL_UNAVAILABLE 계측에 의존한다
D6 복원을 첫 렌더 이전에 await 항상. 복원 시간이 체감 가능해지면 skeleton UI 로 보완하되 순서는 유지 tanstack-query-persistence-hydration-official.md#C5 official-reference 복원이 느린 저사양 기기에서 첫 페인트가 지연된다
D7 성공 query + persistenceTier 로 대상 한정, 직렬화는 adapter 항상. error/pending 캐시를 살려야 할 요구가 생기면 재검토 tanstack-query-persistence-hydration-official.md#C6, #C7 official-reference registry 에 persistenceTier 를 빠뜨린 신규 query 는 조용히 영속되지 않는다 — gate 가 잡아야 함

구현 가이드

2026-07-28 /branch-spec 조사(MDN 2건 + TanStack 공식 2페이지)로 in-scope detail 을 채웠다. 근거가 원칙만 지지하고 detail 은 지지하지 않는 cell 은 UNSUPPORTED_IMPL_DECISION + trade-off 한 줄로 표시했다(CLAUDE.md §15.5 R2). 다른 branch 결정 영역(QueryCachePort 정책, storage key·classification)은 남기지 않았다(R3).

1. CrossTabSyncPort — 채널 하나와 발신자 처리

Trace: D4(로컬 무효화 직접 수행) ← mdn-broadcastchannel-storage-event.md#C2 / D5(대체 경로 없음) ← 같은 문서 #C4·#C10 / 상속 DEC-…-CROSS-TAB-001@2

adapter 는 부팅 시 채널을 한 번 만들고 그 뒤로 바꾸지 않는다. 경로는 두 개뿐이다.

조건 동작
BroadcastChannel 생성 성공 채널 객체를 컨텍스트당 1개 만들어 발행·수신 모두에 쓴다
생성 실패 CROSS_TAB_CHANNEL_UNAVAILABLE 로 표면화하고 탭 내 무효화만 수행한다(§8.2). 대체 전송을 만들지 않는다

발신 탭의 무효화는 채널을 거치지 않고 직접 호출한다. #C2 가 보낸 객체를 수신 대상에서 제외하므로, echo 를 기다리는 구현은 발신 탭만 stale 로 남긴다.

대체 경로를 두지 않는 이유는 #C4 다 — BroadcastChannel 은 2022년 3월부터 모든 주요 브라우저에서 동작하며, 대체가 필요한 환경이 지원 대상에 있다는 근거는 조사에서 나오지 않았다. 유력 후보였던 storage event 경로는 #C10 대로 sessionStorage 에서 다른 탭에 도달하지 않아, 잘못 구현하면 조용히 아무 일도 하지 않는 경로가 된다. 평소 실행되지 않아 테스트로도 걸리지 않는 경로를 미리 만들어 두지 않고, 미동작 환경이 실제로 관측되면(CROSS_TAB_CHANNEL_UNAVAILABLE 계측) 그때 추가한다.

UNSUPPORTED_IMPL_DECISION — 부재 판정을 typeof 검사 + 생성 시도로 하는 것. #C4 는 Baseline 이라고만 말하고 부재를 어떻게 감지하는지는 말하지 않는다. trade-off: 생성까지 시도해야 차단 환경의 예외를 잡을 수 있어 객체 1개 비용을 감수했다.

2. 메시지 봉투

Trace: D2(key 만 전파) ← mdn-broadcastchannel-storage-event.md#C5 / D4 ← #C2

봉투는 두 필드만 갖는다.

  • keys — 무효화할 query key 배열. FE-OC-012 의 registry factory 가 만든 key 만 허용한다. 값·응답 본문·사용자 식별자를 넣지 않는다
  • origin — 발신 컨텍스트 식별자. 수신부가 자기 발신을 걸러내는 2차 방어(1차는 D4 의 채널 자체 제외)

값을 싣지 않는 것은 기술 제약이 아니다. #C5 대로 structured clone 이라 객체를 그대로 보낼 수 있다. 금지하는 이유는 수신 탭이 자기 권한으로 얻지 않은 데이터를 갖게 되기 때문이며, 수신 탭은 key 를 받아 자기 QueryCachePort 로 다시 가져온다.

수신부는 방어적으로 판독한다. 봉투 파싱 실패는 던지지 않고 계측한 뒤 무시한다 — 무효화 신호 하나를 놓치는 것이 UI 를 죽이는 것보다 낫다.

UNSUPPORTED_IMPL_DECISIONorigin 필드로 2차 방어를 두는 것. #C2 만으로 발신자 제외가 보장되므로 원칙적으로는 불필요하다. trade-off: 채널 객체가 실수로 2개 만들어졌을 때 중복 무효화를 막는 안전망이며, 문자열 하나의 비용으로 D4 위반을 런타임에 흡수한다.

3. CachePersistencePort — 파티션 키와 복원 순서

Trace: D1(3-tuple + 폐기) ← tanstack-query-persistence-hydration-official.md#C1·#C2 / D6(렌더 이전 await) ← #C5 / D7(대상 선별·직렬화) ← #C6·#C7 / 상속 DEC-…-SERVER-STATE-001@1

파티션 키는 <releaseId>:<configSchemaVersion>:<apiContractVersion> 단일 문자열로 만들어 스냅샷과 함께 저장한다. 복원 시 이 문자열이 정확히 일치하지 않으면 스냅샷을 폐기하고 빈 캐시로 시작한다. 부분 복원·필드 단위 migration 경로를 두지 않는 것은 #C2 가 채택 라이브러리의 기존 동작(removeClient() 후 즉시 폐기)임을 확인해 준다.

복원 순서는 bootstrap §4.5 의 캐시 복원 단계에서 await 한다. #C5 가 경고하는 경합(복원 중 query mount → fetch)이 정확히 이 순서를 어겼을 때 나타난다. 복원 실패는 부팅을 막지 않는다 — CACHE_PERSISTENCE_FAILURE 로 계측하고 메모리 캐시로 계속한다(§8.2 와 정합).

영속 대상은 FE-REG-QUERY.persistenceTier !== 'memory' 인 행 중 성공한 query 로 한정한다. #C6 의 기본 동작과 같은 방향이며, registry 를 상위 필터로 두어 "라이브러리 기본값이 바뀌면 우리 계약도 바뀌는" 결합을 끊는다. 직렬화는 adapter 책임이다(#C7).

UNSUPPORTED_IMPL_DECISION — 파티션 키를 : 구분 단일 문자열로 만드는 것. #C1 은 buster 가 문자열이라는 것만 말하고 구성·구분자를 말하지 않는다. trade-off: 구조화 객체 대신 문자열을 택해 비교를 동등성 1회로 끝냈다. 비용은 세 값 중 무엇이 불일치했는지 폐기 시점에 알 수 없는 것이며, 이는 telemetry 에 세 값을 따로 실어 보완한다.

4. 이 branch 가 남기지 않는 것 (R3)

엣지·실패·의존

  • 실패·엣지 경로
    • 직렬화·영속·복원 실패 → CACHE_PERSISTENCE_FAILURE, 메모리 캐시만 사용하고 제품 흐름을 막지 않음 (D6)
    • BroadcastChannel 생성 불가 → CROSS_TAB_CHANNEL_UNAVAILABLE, 탭 내 무효화만 수행. 대체 전송을 시도하지 않는다 (D5)
    • 파티션 키 불일치 캐시 발견 → 복원하지 않고 폐기. 부분 복원 금지 (D1)
    • 수신 탭이 무효화 key 를 받았으나 해당 query 를 구독하지 않음 → 무시 (에러 아님)
    • 다중 탭이 동시에 같은 key 를 무효화 → 중복 refetch 를 QueryCachePort 의 dedup 이 흡수해야 함
    • 발신 탭이 자기 무효화를 놓침 → 채널이 발신자에게 echo 하지 않으므로(mdn-broadcastchannel-storage-event#C2) 로컬 무효화는 직접 호출한다 (D4). echo 대기 구현은 이 경로에서 조용히 실패한다
    • 채널 객체를 2개 이상 만든 경우#C2 의 제외 단위가 객체라서 같은 문서의 두 번째 객체가 자기 메시지를 수신해 무효화가 중복 실행된다. adapter 는 컨텍스트당 1개를 강제하고 origin 필드로 흡수한다 (D4)
    • 닫힌 채널에 발행InvalidStateError(#C6). unmount 후 발행 경로가 남아 있다는 신호이므로 삼키지 않고 계측한다
    • 봉투 JSON 파싱 실패 → 던지지 않고 계측 후 무시. 신호 1건 손실이 UI 중단보다 낫다
  • 다른 계약 의존

검증해야 할 주장

Claim Why uncertain How to verify Status
version 불일치 캐시가 복원되지 않는다 부분 복원이 조용히 성공하기 쉬움 negative fixture — 이전 version 스냅샷 주입 후 복원 시도가 폐기로 끝나는지 planned
탭 A mutation 이 탭 B 캐시를 무효화한다 BroadcastChannel 은 같은 origin 의 다른 탭에서만 동작 integration test — 두 컨텍스트에서 발행/수신 확인 planned
BroadcastChannel 부재 시 탭 내 무효화만 하고 조용히 넘어가지 않는다 부재를 감지 못하면 다중 탭 stale 이 무음으로 남는다 fixture — BroadcastChannel 을 undefined 로 만든 뒤 CROSS_TAB_CHANNEL_UNAVAILABLE 계측과 탭 내 무효화 동작 확인 planned
key 만 전파해도 UI 가 일관된다 수신 탭의 refetch 타이밍에 따라 잠깐 어긋날 수 있음 integration test — 전파 후 두 탭의 최종 상태 일치 needs-confirmation
다중 탭 동시 무효화가 refetch 폭주를 만들지 않는다 dedup 이 QueryCachePort 책임인지 이 branch 책임인지 경계가 얇음 부하 fixture — N개 탭 시뮬레이션 후 실제 요청 수 측정 needs-confirmation
지원 대상 브라우저에 BroadcastChannel 미동작 환경이 실제로 있는지 #C4 는 2022-03 이후 모든 주요 브라우저에서 동작한다고 말하지만 지원 대상 목록(FE-Q-007)이 미확정이다. 있으면 CROSS-TAB-001@2 의 "대체 경로 없음" 을 되돌려야 한다 FE-Q-007 확정 후 대조 + CROSS_TAB_CHANNEL_UNAVAILABLE 발생률 관측 needs-confirmation
발신 탭이 자기 무효화를 받지 못한다 #C2 는 명시적이나 구현이 echo 를 기대하기 쉬움 negative fixture — 탭 A 발행 후 탭 A 의 수신 handler 가 호출되지 않는지 planned
세 version 중 무엇이 실제로 캐시를 무효화해야 하는지 세 값을 모두 넣은 것은 project-local 판단이며 과잉일 수 있음 배포 로그 대조 — 각 값이 단독으로 바뀐 배포에서 stale 캐시가 실제 문제를 냈는지 needs-confirmation
복원 await 가 첫 페인트를 유의미하게 늦추지 않는다 #C5 는 경합만 말하고 비용은 말하지 않음 저사양 기기에서 복원 유/무 FCP 비교 needs-confirmation

Audit & Findings

/branch-spec 2026-07-28 조사에서 발견한 상위 계약과 조사 결과의 불일치. FE-D028 은 hub 소유이므로 이 branch 는 정합 권고만 남기고 자동 수정하지 않는다(hub §3.3).

Finding ID 대상 현재 서술 조사 결과 권고 처리
FALLBACK_JUSTIFICATION_GAP hub FE-D028 / DEC-…-CROSS-TAB-001@1 "BroadcastChannel 우선에 storage event fallback" — fallback 이 필요한 환경을 특정하지 않는다 mdn-broadcastchannel-storage-event#C4: "It's been available across browsers since March 2022" (Baseline Widely available). fallback 은 localStorage write·5 MiB 예산·연속 동일값 회피 장치를 추가로 요구한다 근거 없는 대체 경로를 유지하지 말고 삭제할 것. 필요해지면 그때 추가 resolved 2026-07-28 — 사용자 결정으로 fallback 삭제. CROSS-TAB-001 revision 1→2(behavior-change), 이 노트의 D5·§구현 가이드·§엣지·TODO 동기화
STORAGE_EVENT_SCOPE_UNSPECIFIED hub FE-D028 및 §4.2 adapters/cross-tab "storage event fallback" 이라고만 적어 backend 를 명시하지 않는다 mdn-broadcastchannel-storage-event#C10: sessionStoragestorage event 는 "not other tabs" — 탭 간 신호로 동작하지 않는다 서술을 좁히거나, 위 finding 대로 경로 자체를 삭제할 것 resolved 2026-07-28 — fallback 삭제로 해소. 고칠 대상 자체가 사라졌다
NO_GROUND_TRUTH /branch-spec §2 ca-tmpl 대조 명령은 /home/donghyeon/workspace/ca-tmpl 의 registry·코드와 대조하라고 요구 ca-tmpl 은 Gradle/Java 백엔드 전용. frontend 구현 repo 는 hub §0.3 대로 미식별 이 branch 의 모든 명세는 planned. actually-implemented 승격은 frontend repo 식별(FE-Q-001) 이후 openFE-Q-001 선행

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

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.

# 관심사 판정 근거
1 문제와 실패 모드가 구체적인가 covered-here §목표 — 탭 간 stale 과 release 를 넘은 캐시 파싱 실패. §엣지에 10개 실패 경로
2 상속한 결정을 실제로 적용했는가 covered-here CROSS-TAB-001@2 → D2·D5, SERVER-STATE-001 → D1·D7, CAPABILITY-001CAP_FE_CACHE_PERSISTENCE 소유
3 project-wide default 와 limit covered-here D1(3-tuple 폐기), D4(echo 없음), D5(대체 경로 없음), D6(렌더 이전 await), D7(성공 query 한정)
4 대안을 검토했는가 covered-here §결정 사항 — releaseId 단독 / Web Locks 리더 / storage event 대체 경로(삭제 결정, §Audit). D3 는 반증까지 기록
5 금지 구현 covered-here §구현 가이드 2 — 값·응답 본문·사용자 식별자 전송 금지, factory 밖 key 금지. §구현 가이드 1 — 대체 전송 신설 금지
6 실패 경로가 error kind 로 매핑되는가 covered-here CACHE_PERSISTENCE_FAILURE·CROSS_TAB_CHANNEL_UNAVAILABLE (hub §8.2 와 정합)
7 관측 가능한가 should-fix 파티션 폐기·fallback 진입·채널 부재를 구분할 telemetry event 를 FE-REG-TELEMETRY 에 등록하지 않았다. D1 의 "세 값 중 무엇이 불일치했는지" 보완도 여기에 걸린다
8 위임 경계가 명확한가 covered-here DELEG-FE-009 (storage registry), §구현 가이드 4 의 R3 목록
9 검증 수단이 있는가 covered-here §검증해야 할 주장 11행, FE-GATE-028 fixture + cache tier report
10 말할 수 있는 범위 covered-here 전 항목 plannedNO_GROUND_TRUTH 로 구현 repo 미식별

판정: Covered (missing 0) · Should-fix 1건(관심사 7 — telemetry event 미등록). Blocking 아님.

마주친 문제

없음.

묶음 (이 branch에서 파생된 자료)

Sub-branches (세부 작업)

아직 없음.

오류 기록 (이 branch 작업 중 발생)

아직 없음.

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

아직 없음.

강의 (이 작업을 위해 학습한 강의)

아직 없음.

job-posting tie-ins (이 작업에서 파생된 글감)

아직 없음.

관련 일일 노트

  • 아직 없음

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: 없음
    • locally-verified 항목: 없음
    • prod-verified 항목: 없음
  • 추출하지 않을 항목: 현재 전 항목 planned