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>
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를 임의로 삭제하지도 않는다.
activateRelease와cleanupOwned는 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비어 있지 않음)는 반드시allowedResponseHeaderNames에vary를 포함해야 한다. 아니면 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. 최초 확인
- 영향 release ID, browser family/version과 최초 발생 시각을 확인한다.
cache.operation.failed와storage.operation.failed의 allowlisted operation/outcome/reason만으로 범위를 좁힌다.- runtime status가
ACTIVE_BROADCAST,ACTIVE_STORAGE_FALLBACK,DEGRADED_LOCAL_ONLY,CLOSED중 무엇인지 확인한다. - 현재 tab의 mutation 성공 후 local namespace가 stale 처리되는지 확인한다.
- 다른 tab의 오래된 화면이 단순 stale 표시인지, 실제 server authorization 우회인지 분리한다. authorization 우회는 cache incident가 아니라 P1 security incident다.
- affected release의 query registry topic/version과 release cache epoch가 일치하는지 확인한다.
3. Cross-tab invalidation degradation
신호
BROADCAST_OPEN_FAILEDBROADCAST_PUBLISH_FAILEDSTORAGE_LISTENER_FAILEDSTORAGE_PUBLISH_FAILED- runtime status
DEGRADED_LOCAL_ONLY
확인
- browser policy, embedded/sandbox context 또는 privacy mode가 BroadcastChannel/Web Storage를 제한하는지 확인한다.
- BroadcastChannel publish 실패 뒤 fixed localStorage pulse key로 정확히 한 번 fallback하는지 확인한다.
- publishing tab은
storageevent를 받지 않으므로 local invalidation을 coordinator가 직접 수행했는지 확인한다. - 다른 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 증가
확인
- 같은 source/epoch의 sequence gap인지 새 page epoch의 정상 sequence reset인지 구분한다.
- duplicate가 BroadcastChannel과 storage fallback 양쪽에서 들어온 것인지 확인한다.
- mutation lease가 유지되는 동안 remote hint가 topic별로 coalesce되는지 확인한다.
- 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_EXCEEDEDSIZE_LIMIT_EXCEEDEDVALUE_REJECTEDSTORAGE_UNAVAILABLE
확인
- 실패 key가 registry backend/value codec/schema/TTL과 일치하는지 확인한다.
- value가 기본 16,384-byte hard cap 안인지 확인한다.
- corrupt/expired record가 exact physical key에서만 제거되고 다른 application key는 유지되는지 확인한다.
COLOR_SCHEMEquota fallback이 동일 serialized envelope와 TTL 규칙을 쓰는지 확인한다.- 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를 충족한다.