Files
tech-log-frontend/docs/operations/client-cache-and-storage-recovery.md
DongHyeonkaandClaude Opus 5 4bff9ca151 chore: sync the frontend template from 4dc033c to 8157ad4
The product was materialized from the template at `4dc033c` and has stayed
on it through 43 template commits, so it was missing all three rounds of
adapter remediation — including files it never had, such as the shared
`abortable-operation` primitive and the `exact-snapshot` decoder that
later fixes are written against. Taking only the newest round was not
possible for that reason: the delta is coherent only as a whole.

The product had not touched `src/adapters` at all since materialization,
so the 140-file delta applied with a three-way merge and no conflicts.
`package.json` was the single overlap and merged cleanly: the product owns
`name`, the template contributed `check:adapter-inventory`,
`check:remediation-ledger` and the image-resolve-signal type fixture.
All 24 product-owned files — README, index.html, CI workflow, i18n
catalog, home page, generated schemas, evidence scripts, component and
visual snapshots — are byte-identical to `main`.

`template.lock.json` now pins the synced revision and tree.

Verified in this repository, not inherited from the template: six type
projects, lint, nine gates (adapter inventory, remediation ledger,
registries, diagnostics, realtime boundaries, architecture, browser
file/storage boundaries, optional recipes, documentation), the production
build, and 2,054 of 2,073 tests. The 19 failures are all in
`tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing
sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template
records; four suites that failed once under parallel load pass in
isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 12:04:58 +09:00

10 KiB

Client cache and Web Storage recovery

  • 적용 대상: production-composed TanStack memory cache, registered Web Storage, cross-tab invalidate-only runtime
  • 비대상: query persistence와 IndexedDB query cache
  • 기준일: 2026-07-28

현재 query persistence는 정책과 runtime에서 disabled다. 기존 IndexedDB reference runtime은 query cache에 조립되지 않았으므로 query-cache incident에서 database 삭제, migration 또는 hydration 조치를 수행하지 않는다. session/account Query lifecycle, strict query policy와 Web Storage v2 lifecycle도 현재 DESIGNED_NOT_IMPLEMENTED다. 아래 목표 절차를 현재 runtime의 보장으로 해석하지 않는다.

Public cache staging repair와 offline activation (STO-03 ~ STO-05)

  • release marker는 "staging이 끝났다"는 주장이고 모든 entry의 존재·digest 증거가 아니다. 같은 manifest로 stageRelease를 다시 호출하면 runtime이 candidate를 재검증하고, browser eviction이나 부분 손상이 발견되면 그 owned candidate만 삭제한 뒤 network에서 다시 stage한다. marker만 보고 성공을 반환하지 않는다.
  • 검증 중 abort나 읽기 불가(UNKNOWN)는 stage 성공이 아니며 active pointer를 건드리지 않는다. candidate를 임의로 삭제하지도 않는다.
  • activateReleasecleanupOwned는 network I/O가 없다. fetcher 없이도 동작하므로 offline rollback과 quota recovery cleanup이 UNSUPPORTED로 막히지 않는다. 두 operation은 Cache Storage와 mutation lock만 요구하고 실패 시 recovery는 RETRY다. stageRelease만 fetcher를 요구하며 recovery는 ONLINE_ONLY다.
  • variant를 사용하는 policy(allowedVaryHeaderNames 비어 있지 않음)는 반드시 allowedResponseHeaderNamesvary를 포함해야 한다. 아니면 composition이 TypeError로 즉시 실패한다. 저장된 variant가 같은 key로 충돌하는 상태를 만들지 않기 위한 cross-field invariant다.

1. 변경할 수 없는 복구 원칙

  • 서버가 server state와 authorization의 source of truth다.
  • cross-tab event는 invalidate hint다. delivery나 server commit 증명이 아니다.
  • remote event로 queryClient.clear(), account logout 또는 credential 폐기를 수행하지 않는다.
  • BroadcastChannel 실패 후 localStorage fallback도 실패하면 DEGRADED_LOCAL_ONLY가 정상 fallback이다. 현재 tab의 local invalidation과 server request는 계속 동작해야 한다.
  • origin 전체 localStorage.clear()deleteDatabase()를 복구 명령으로 사용하지 않는다.
  • query key, storage value, event/topic/epoch/source ID, user/account/tenant ID를 incident log에 복사하지 않는다.
  • incident 대응 중 QUERY_PERSISTENCE를 켜거나 server response를 Web Storage에 저장하지 않는다.

2. 최초 확인

  1. 영향 release ID, browser family/version과 최초 발생 시각을 확인한다.
  2. cache.operation.failedstorage.operation.failed의 allowlisted operation/outcome/reason만으로 범위를 좁힌다.
  3. runtime status가 ACTIVE_BROADCAST, ACTIVE_STORAGE_FALLBACK, DEGRADED_LOCAL_ONLY, CLOSED 중 무엇인지 확인한다.
  4. 현재 tab의 mutation 성공 후 local namespace가 stale 처리되는지 확인한다.
  5. 다른 tab의 오래된 화면이 단순 stale 표시인지, 실제 server authorization 우회인지 분리한다. authorization 우회는 cache incident가 아니라 P1 security incident다.
  6. affected release의 query registry topic/version과 release cache epoch가 일치하는지 확인한다.

3. Cross-tab invalidation degradation

신호

  • BROADCAST_OPEN_FAILED
  • BROADCAST_PUBLISH_FAILED
  • STORAGE_LISTENER_FAILED
  • STORAGE_PUBLISH_FAILED
  • runtime status DEGRADED_LOCAL_ONLY

확인

  1. browser policy, embedded/sandbox context 또는 privacy mode가 BroadcastChannel/Web Storage를 제한하는지 확인한다.
  2. BroadcastChannel publish 실패 뒤 fixed localStorage pulse key로 정확히 한 번 fallback하는지 확인한다.
  3. publishing tab은 storage event를 받지 않으므로 local invalidation을 coordinator가 직접 수행했는지 확인한다.
  4. 다른 tab은 focus/reconnect 또는 명시적 refresh에서 server를 다시 읽는지 확인한다.

안전한 조치

  • local-only 상태에서는 사용자에게 현재 tab refresh action을 유지한다.
  • fallback regression이 특정 release에서 시작됐으면 이전 compatible release로 rollback한다.
  • event payload에 query key/data를 추가하거나 TTL/size/source tracking limit을 임시 확대하지 않는다.
  • 현재 runtime에는 별도 dynamic kill switch가 없다. 존재하지 않는 flag로 복구됐다고 선언하지 않는다.

4. Duplicate, sequence gap과 refetch 증가

신호

  • DUPLICATE, STALE, sequence-gap observation 증가
  • 여러 namespace의 active refetch 동시 증가
  • backend read traffic 증가

확인

  1. 같은 source/epoch의 sequence gap인지 새 page epoch의 정상 sequence reset인지 구분한다.
  2. duplicate가 BroadcastChannel과 storage fallback 양쪽에서 들어온 것인지 확인한다.
  3. mutation lease가 유지되는 동안 remote hint가 topic별로 coalesce되는지 확인한다.
  4. gap에서 inactive query까지 즉시 refetch하거나 remote hint를 다시 publish하는 echo가 없는지 확인한다.

안전한 조치

  • gap은 등록 namespace를 stale 처리하고 active query만 refetch한다.
  • backend가 압박을 받으면 API degradation runbook의 server read 보호 정책을 적용한다. client query와 HTTP 양쪽 retry를 동시에 늘리지 않는다.
  • persistent event queue나 localStorage counter를 급히 추가하지 않는다.

5. Web Storage quota, corruption과 denial

신호

  • STORAGE_QUOTA_EXCEEDED
  • SIZE_LIMIT_EXCEEDED
  • VALUE_REJECTED
  • STORAGE_UNAVAILABLE

확인

  1. 실패 key가 registry backend/value codec/schema/TTL과 일치하는지 확인한다.
  2. value가 기본 16,384-byte hard cap 안인지 확인한다.
  3. corrupt/expired record가 exact physical key에서만 제거되고 다른 application key는 유지되는지 확인한다.
  4. COLOR_SCHEME quota fallback이 동일 serialized envelope와 TTL 규칙을 쓰는지 확인한다.
  5. native exception message나 value가 diagnostics에 포함되지 않았는지 확인한다.

안전한 조치

  • preference persistence 실패는 memory fallback 또는 safe default로 degrade한다.
  • exact corrupt/expired registered key만 제거한다.
  • credential key를 임시 storage key로 재등록하지 않는다.
  • origin 전체 clear, arbitrary key enumeration과 query-cache persistence 전환을 금지한다.

6. Logout, account 전환과 stale data

현재 구현은 release cache epoch만 제공하고 session/account epoch와 opaque account partition은 아직 없다. 따라서 다음을 과장해 보장하지 않는다.

  • cross-tab hint가 모든 tab의 logout을 완료했다.
  • old in-flight result가 새 account runtime에 기록되지 않는다.
  • persisted query data가 account별로 분리된다.

실제 account-switching 제품을 배포하기 전에 session owner 기반 local admission fence, old QueryClient cancel/clear, late-result generation fence와 account epoch를 구현해야 한다. 그 전에는 external auth owner와 서버 authorization을 authoritative하게 유지하고, 의심되는 stale personal data incident는 security owner에게 escalation한다.

VD-13 lifecycle이 구현된 제품의 안전한 전환 순서는 다음과 같다.

local session authority revoke
  -> old generation FENCED
  -> new query/mutation admission 거절
  -> in-flight cancel
  -> provider/listener detach
  -> old QueryClient clear + dispose
  -> old Web Storage/optional persistence partition purge policy
  -> 새 opaque scope와 새 QueryClient remount

각 async callback은 terminal cache write 직전에 captured generation을 다시 검증한다. broadcast logout 수신 여부와 무관하게 local 전환이 완료되어야 한다. optional query persistence를 선택한 제품은 durable namespace epoch/CAS와 old record resurrection fault까지 통과한 경우에만 restore를 다시 연다.

7. Cleanup과 종료

정상 dispose는 coordinator subscription, BroadcastChannel, storage listener, dedupe/high-watermark state와 pending remote set을 idempotent하게 정리한다.

확인:

  • HMR/unmount 반복 뒤 listener/channel 수가 증가하지 않음
  • dispose 뒤 event가 query invalidation을 실행하지 않음
  • fixed pulse key가 publish 후 best-effort 제거됨
  • cleanup failure가 cached value나 identifier를 log하지 않음

8. 복구 완료 조건

  • current tab의 committed mutation 후 local namespace invalidation 성공
  • available transport에서는 two-tab remote invalidation 성공
  • unavailable transport에서는 explicit DEGRADED_LOCAL_ONLY와 manual/focus revalidation 성공
  • duplicate/self/stale event가 추가 refetch를 만들지 않음
  • sequence gap은 bounded active-query reconciliation으로 종료
  • Web Storage corrupt/quota scenario가 exact-key cleanup 또는 documented fallback 으로 종료
  • raw key/value/query/user identifier가 diagnostic evidence에 없음
  • affected browser의 manual two-tab smoke 기록 보존

two-page native cross-context transport suite는 존재하지만 현재 보존 evidence는 Chromium/Firefox에 한정되고, production QueryClient/coordinator의 account lifecycle과 late-result fence까지 연결한 end-to-end case는 아직 없다. 따라서 manual smoke나 transport-only suite를 production promotion evidence의 영구 대체물로 사용하지 않는다. Chromium/Firefox/WebKit에서 scope 전환을 포함한 동일 case set이 통과할 때 이 runbook의 promotion close criteria를 충족한다.

9. 관련 문서