Files

803 lines
29 KiB
Markdown

# VD-25: Server State Cache lifecycle
- 상태: Accepted — reference bound-query/input-aware mutation baseline composed, lifecycle delta pending
- 결정일: 2026-07-28
- TanStack Query memory runtime: `COMPOSED`
- reference bound-query/profile/input-aware duplicate coordination: `COMPOSED`
- session-generation/identity lifecycle: `COMPOSED`
- conditional sidecar/optimistic layer/cursor runtime: `AVAILABLE_NOT_COMPOSED`
- account projection/infinite-query/effect reconciliation delta:
`DESIGNED_NOT_IMPLEMENTED`
- normalized graph cache: `NOT_SELECTED`
- query persistence product selection: `NOT_SELECTED`
- 관련 결정: VD-13, VD-23, VD-24, VD-26, VD-27, VD-29, VD-30
- 상세 설계:
[API contract, Schema, Mapper와 Server State](../api-contract-schema-mapper-and-server-state.md)
## 배경
현재 production bootstrap은 runtime별 QueryClient와 invalidation coordinator를
실제로 조립한다. reference presentation은 application input을
`useApplicationQuery()`/`useApplicationMutation()`에 연결하며 다음을 제공한다.
- AbortSignal cancellation
- finite global stale/gc default
- Query network retry off
- stale-degraded 표시
- exact-key optimistic snapshot/rollback
- conflict 표면
- mutation topic lease와 cross-context invalidate-only hint
reference list/detail은 bound definition이 key, executor와 profile을 함께
제공한다. strict canonical codec은 depth/node/string/encoded-byte, cycle/shared
reference, undefined/NaN/negative-zero, sparse array, accessor와 non-plain object를
닫고 runtime-private opaque identity만 query key에 넣는다. profile의 stale/gc/
refetch/retry owner와 result item/byte admission이 실제 hook에 적용된다.
current invalidation definition은 namespace당 singular topic이고 같은 topic의
multi-namespace fan-out을 compose하지 못한다. 아래 bounded topic-set registry는
target delta이며 현재 runtime 증거가 아니다.
production scope runtime은 session transition 즉시 old generation을 fence하고
QueryClient cancel/clear 뒤 새 opaque scope를 발급한다. identity registry는 active
lease, refcount, bounded LRU, canonical-byte/entry ceiling과 token collision 검사를
scope별로 소유한다. mutation duplicate baseline도 scope exact semantic input만
join하고 late result를 폐기한다.
ordered optimistic layer, conditional validator CAS sidecar와 bounded cursor chain은
실행 가능한 test runtime까지 존재하지만 reference backend/definition에는 아직
연결하지 않았다. 남은 부분은 account identity projection, logical-key serialization,
HTTP 304 transaction, product optimistic membership/revision, infinite-query binding과
effect certainty reconcile이다.
## 소유권
VD-13:
- `CacheScopeSnapshot`
- ORIGIN/ACCOUNT/SESSION projection
- QueryClient generation/fence/remount
- strict canonical key codec 공통 구현
- cross-tab wire와 durable namespace epoch
- optional IndexedDB persistence와 restore
- logout/account switch purge
VD-25:
- operation/application query definition binding
- per-query freshness/gc/refetch/result budget
- cache admission과 mapped value contract
- cursor/infinite pagination
- conditional revalidation integration
- mutation concurrency, optimistic patch와 reconciliation
- invalidation/seed policy
- transport-independent error/stale behavior
VD-25는 VD-13의 scope snapshot/key codec을 소비하고 다른 epoch/fingerprint
protocol을 만들지 않는다.
## 결정
### 1. TanStack Query가 유일한 기본 Server State owner다
REST, GraphQL과 gRPC-Web unary result는 transport-independent application
projection으로 mapping된 뒤 TanStack Query memory cache에 들어갈 수 있다.
기본적으로 설치하지 않는다.
- Redux/Zustand server entity copy
- Apollo/urql normalized cache
- raw HTTP response cache wrapper
- generated client SDK cache
- custom Map singleton
GraphQL normalized cache가 실제로 필요하면 bounded context에서 TanStack
operation-result cache를 대체하는 별도 ADR을 승인한다. 두 cache에 같은 entity를
동시 write하지 않는다.
browser HTTP cache/Cache Storage, TanStack Query memory와 IndexedDB query
persistence는 서로 다른 owner다.
### 2. Bound Query Definition
caller가 query key와 executor를 독립적으로 조립하지 않는다.
```text
QueryDefinition<Input, Value>
definitionId
owner
operationId
inputCodec
keyCodecId
serverStateProfileId
scopePersistencePolicyId
resultContractId
execute(validatedInput, executionContext)
```
binding:
```text
bindQuery(definition, rawInput, CacheScopeSnapshot)
-> validate/canonicalize input
-> derive branded query key
-> resolve immutable policy
-> freeze execute closure and captured generation
-> BoundQuery<Value>
```
presentation API:
```text
useApplicationQuery(boundQuery)
```
`queryKey`, `queryFn`, stale/gc/retry와 arbitrary TanStack option을 feature page에서
따로 넘기지 않는다. escape hatch가 필요하면 새 profile을 먼저 등록한다.
### 3. Semantic query identity
query key는 VD-13의 단일 normative layout을 그대로 사용한다.
```text
[
"query",
keySchemaVersion,
scopeProjectionFingerprint,
namespaceName,
namespaceVersion,
queryDefinitionVersion,
canonicalSemanticInput
]
```
- namespace/key schema/version과 scope projection은 VD-13 profile-owned
- query definition version과 semantic input projection은 VD-25 definition-owned
- scope projection은 VD-13의 exact profile output
- input은 strict codec의 plain immutable representation
- REST URL/query string, ETag와 cursor raw value를 root identity에 넣지 않음
- GraphQL document/persisted hash를 넣지 않음
- protobuf bytes/generated message를 넣지 않음
- presentation locale/formatted string을 넣지 않음
transport migration이 use-case/result meaning을 보존하면 semantic query family를
유지할 수 있다. schema/mapper meaning, scope나 output identity가 바뀌면
query-definition/release epoch를 바꾼다.
strict key codec은 다음을 거절한다.
- `undefined`, sparse array
- NaN, Infinity, negative zero policy mismatch
- bigint/symbol/function
- Date/Map/Set/RegExp/typed array/native/class instance
- accessor/proxy/prototype pollution key
- cycle/shared-reference ambiguity
- non-plain object
- depth/node/part/string/encoded-byte ceiling 초과
query key에 PII/business ID를 직접 넣지 않는다. 필요한 resource identity는
feature policy가 발급한 opaque bounded token으로 투영한다.
cursor와 command 동일성은 raw value나 충돌 가능 digest만으로 판정하지 않는다.
```text
RuntimeIdentityTokenCodecV1
canonicalCodecVersion
maxCanonicalBytes
maxInternEntries
maxInternCanonicalBytes
tokenEntropyBits >= 128
lifetime = RUNTIME_SCOPE
```
- strict length-prefixed typed canonical encoding이 exact equality source다.
- runtime-private intern table이 canonical bytes를 opaque random token에
일대일로 binding하고 token collision을 reverse map으로 검사한다.
- 같은 token 후보가 다른 canonical bytes와 충돌하면 새 token을 발급한다. bounded
재시도 후에도 해결되지 않으면 `IDENTITY_TOKEN_COLLISION`으로 admission/join을
fail-closed한다.
- intern row는 lease/refcount를 가진다. Query entry가 설치된 동안, active
observer/fetch와 mutation/join이 진행되는 동안 해당 token을 eviction하지 않는다.
- Query removal/GC에서 query token lease를, mutation terminal/join waiter
settlement에서 command token lease를 exact once release한다. runtime/scope
close는 남은 table을 전부 폐기한다.
- refcount 0 row만 bounded LRU eviction할 수 있다. entry 수 또는 total canonical
bytes ceiling을 active lease 때문에 회수할 수 없으면
`IDENTITY_INTERN_LIMIT_EXCEEDED`로 신규 cache/command admission을 fail-closed한다.
- canonical bytes/raw cursor/command input은 query key, diagnostics,
cross-context wire와 persistence에 넣지 않고 runtime/scope close에서 폐기한다.
- token은 backend idempotency key, authorization proof나 durable identity가 아니다.
### 4. ServerStateProfile
```text
ServerStateProfileV1
profileId
classification
scopePersistencePolicyId
staleTimeMs
gcTimeMs
refetchOnMount
refetchOnFocus
refetchOnReconnect
networkMode
retryOwner = TRANSPORT | QUERY | NONE
maxResultItems
maxEstimatedResultBytes
paginationProfileId | null
conditionalProfileId | null
placeholderPolicy
initialFailurePolicy
refreshFailurePolicy
authorizationFailurePolicy
contractFailurePolicy
invalidationTopicRefs[] = { topicId, topicVersion }
owner
```
implementation ceilings:
- query `invalidationTopicRefs` set/version은 joined VD-13
`QueryScopePersistencePolicy`와 exact match
- `gcTimeMs`는 inactive retention이고 `staleTimeMs`는 freshness이므로
`staleTimeMs <= gcTimeMs`를 일반 불변조건으로 강제하지 않는다.
- VD-13 persistence를 선택한 경우에만 restore `maxAgeMs`, retention과
`gcTimeMs`의 join compatibility를 검증한다.
- finite gc 기본, `Infinity`는 explicit immortal-static profile만
- stale/gc 최대값
- result item/estimated byte 상한
- maximum pages
- refetch trigger storm coalescing
- foreground/background concurrency
현재 global 30초/5분은 reference default이지 모든 product query의 production
정책이 아니다.
### 5. Retry owner
한 network operation에는 retry owner가 정확히 하나다.
| 상황 | 기본 owner |
| --- | --- |
| installed REST | REST transport |
| persisted GraphQL | GraphQL transport |
| Connect unary | Connect adapter 또는 selected edge 중 exact one |
| gRPC-Web unary | gRPC-Web transport |
| pure local query computation | Query 또는 none |
transport retry가 있는 definition은 TanStack `retry=false`다. Query retry callback이
same gateway call을 다시 실행해 transport attempts를 배가하지 않는다.
manual UI retry는 새 logical query execution이다. keyed command의 ambiguous outcome을
query retry처럼 재실행하지 않는다.
### 6. Cache admission
query cache에 admission 가능한 값:
- VD-24 mapper가 만든 immutable plain application read model
- exact result contract/version
- current scope/runtime generation
- item/estimated-byte ceiling 안
- complete result 또는 operation이 허용한 typed partial result
금지:
- raw JSON/GraphQL envelope
- generated protobuf message/client
- Response/ReadableStream
- auth/CSRF/idempotency/cursor/validator/trace metadata
- thrown Error/AppFailure detail payload
- function/class/domain service/native object
admission 순서:
```text
transport success
-> schema
-> mapper
-> result budget
-> scope/generation fence
-> Query commit
```
어느 단계든 실패하면 cache write 0회다.
### 7. Result size
`maxEstimatedResultBytes`는 memory reservation이 아니라 hard admission guard다.
- mapper output plain data를 bounded estimator로 측정
- string UTF-8 bytes, key overhead, array/object node count를 보수적으로 합산
- cycle/class/native/accessor는 측정 전에 거절
- 측정 자체가 deadline/node ceiling을 넘으면 admission 거절
- result cap을 넘겨도 transport success를 unbounded UI state로 반환하지 않고
`RESULT_LIMIT_EXCEEDED` 또는 server pagination requirement로 닫음
large binary/collection은 streaming/file 또는 cursor page capability로 이동한다.
### 8. Query lifecycle
```text
IDLE
-> LOADING
-> SUCCESS | EMPTY | TERMINAL_ERROR
SUCCESS | EMPTY
-> REFRESHING
-> SUCCESS | EMPTY
-> STALE_DEGRADED
-> TERMINAL/REAUTH when policy forbids stale visibility
```
current AsyncState의 base와 overlay 구분을 유지한다.
failure policy:
| failure | default |
| --- | --- |
| transient network/5xx refresh failure | valid previous data + stale-degraded |
| caller/navigation abort | terminal error로 표시하지 않음 |
| auth required | sensitive/account query는 stale 숨김, reauth |
| forbidden/account switch | current scope data 즉시 숨김/clear |
| schema/mapper/contract mismatch | cache write 금지, default stale 숨김 또는 explicit safe-static exception |
| rate limit | previous data 정책 + retry-after UX |
| not found | feature policy에 따라 empty/remove/tombstone |
query profile이 sensitive stale data를 계속 보여 주는 결정을 global fallback으로
상속하지 않는다.
### 9. Freshness와 refetch
`staleTime`은 business correctness/authorization TTL이 아니다.
- focus/reconnect/mount refetch는 profile별
- simultaneous trigger는 one in-flight query로 coalesce
- minimum refetch interval과 deadline 적용
- visibility offline state는 hint이며 server revision을 대체하지 않음
- response age/cache-control을 staleTime으로 자동 변환하지 않음
- backend push/invalidation은 stale hint이며 authoritative refetch를 시작
freshness-sensitive command/read-after-write는 mutation receipt/revision 또는
authoritative refetch contract를 사용한다.
### 10. Conditional revalidation
REST app-managed ETag profile만 internal validator metadata를 사용할 수 있다.
```text
ValidatorBinding
query definition/fingerprint
scope fingerprint
runtime generation
representation version
opaque validator
```
- raw validator는 query value/key/diagnostics에 넣지 않음
- 304는 exact binding + existing mapped cache value가 있을 때만 fresh transition
- 304 freshness transition은 같은 query-entry cache revision에 CAS가 성공할 때만
commit하며, concurrent 200/removal 뒤의 late 304를 폐기
- value가 없거나 wrong generation이면 304 success로 만들지 않음
- validator mismatch/full 200은 normal schema/mapper/admission을 다시 수행하고
mapped value commit과 validator install/update를 하나의 entry transaction으로
취급
- Query removal/GC, scope/logout/account switch, release/contract/schema/mapper
epoch 변경과 incompatible cache clear에서 validator sidecar도 함께 폐기
- ordinary invalidation 때 validator를 conditional refetch까지 보존할지 즉시
폐기할지는 profile이 고정하며 query entry와 독립적으로 남기지 않음
GraphQL/gRPC metadata를 arbitrary ETag로 해석하지 않는다. application revision
field를 schema/mapper가 명시적으로 제공한 경우 별도 revalidation policy가
사용한다.
### 11. Cursor pagination
```text
PaginationProfile
CURSOR_SINGLE_PAGE
CURSOR_INFINITE
OFFSET_STABLE
```
cursor page:
```text
CursorPage<T>
items
nextCursor | null
hasMore
snapshotToken | null
```
page invariant:
- `hasMore === (nextCursor !== null)`을 codec에서 강제한다.
- page item count는 requested/implementation ceiling 이하다.
- chain 안의 snapshot token은 provider profile이 허용한 null/동일 값만 사용한다.
- `hasMore=true`인 empty/non-progress page는 explicit sparse-page profile이 없으면
contract failure다.
`CURSOR_INFINITE` root key:
- filters/sort/page size semantics
- scope
- page definition version
- cursor 제외
`CURSOR_INFINITE` page parameter:
- adapter-private/opaque bounded cursor
- previous page/snapshot binding
- raw cursor를 diagnostics/URL state/persistence에 임의 저장하지 않음
`CURSOR_SINGLE_PAGE`:
- first page의 null marker 또는 current cursor의 runtime-scoped non-reversible
identity token을 `canonicalSemanticInput`에 포함한다.
- raw cursor는 bound executor closure에만 두며 query key/value/diagnostics에 넣지
않는다.
- runtime-private exact equality guard/token binding이 실패하면 single-page cache
admission을 끄고 closed failure로 끝낸다.
- runtime-scoped token을 쓰는 `CURSOR_SINGLE_PAGE``MEMORY_ONLY`다.
infinite policy:
- max pages
- max total items
- max estimated bytes
- repeated cursor/non-progress/loop detection
- page eviction direction
- refresh strategy: first page only, visible window 또는 complete bounded chain
- item stable identity/duplicate/revision conflict policy
- snapshot changed 시 old/new page를 섞지 않고 restart
pagination persistence는 기본 disabled다. `CURSOR_SINGLE_PAGE`를 durable하게
만들려면 stable partition-bound keyed codec/key lifecycle을 별도 ADR로 승인해야
하며 runtime token을 persistence key로 재사용하지 않는다. `CURSOR_INFINITE`
persistence를 선택하려면 VD-13 profile이 cursor와 snapshot의
classification/expiry, maximum persisted pages/bytes, restored `pageParams` 사용
여부를 명시적으로 승인해야 한다. raw sensitive cursor 또는 만료 후 page
parameter를 IndexedDB에 저장하지 않는다.
offset pagination은 insert/delete drift를 허용하는 dataset에서 사용하지 않는다.
### 12. Mutation Definition
```text
MutationDefinition<Input, Value>
definitionId
operationId
inputCodec
logicalKeyCodec
commandIdentityTokenCodecId
concurrencyPolicy
duplicatePolicy
optimisticPolicyId | null
invalidationTopicRefs[] = { topicId, topicVersion }
seedPolicyId | null
conflictPolicyId
effectCertaintyPolicy
owner
```
presentation:
```text
useApplicationMutation(boundMutation)
```
caller는 raw optimistic query key/update function과 invalidation topic을 조립하지
않는다.
mutation topic ref는 unique 0..16개이고 모두 VD-13 global topic registry의 exact
version으로 resolve돼야 한다. 한 ref가 가리키는 bounded namespace set만
invalidate하며 caller가 runtime에 topic을 추가하지 못한다.
### 13. Mutation concurrency
```text
ConcurrencyPolicy
PARALLEL
SERIAL_BY_LOGICAL_KEY
SUPERSEDE_PENDING_READ_BY_LOGICAL_KEY
REJECT_WHILE_ACTIVE_BY_LOGICAL_KEY
DuplicatePolicy
JOIN_IDENTICAL
REJECT_DUPLICATE
ALLOW_INDEPENDENT
```
- logical key는 validated input의 approved opaque identity
- command identity token은 operation/definition version, current scope
partition과 **전체 validated semantic input**을
`RuntimeIdentityTokenCodecV1`으로 intern해 만든다. UI transient field와
transport bytes/idempotency key는 canonical equality source에 포함하지 않는다.
- logical key는 serialization/conflict group이고 exact canonical equality +
identity token은 동일 command 판별 값이다. 두 값을 서로 대체하지 않는다.
- `JOIN_IDENTICAL`은 exact equality guard도 통과한 같은 identity token의 기존
in-flight Promise와 terminal result를 공유하며 transport/optimistic layer를
추가하지 않는다.
- `REJECT_DUPLICATE`는 exact-identical token이 active이면 fetch 0회와 closed
`DUPLICATE_IN_FLIGHT`를 반환한다.
- `ALLOW_INDEPENDENT`는 같은 exact identity도 독립 command로 실행한다. backend
replay/idempotency와 UX가 이를 명시적으로 허용한 operation에만 등록한다.
- distinct input을 같은 Promise에 join하지 않음
- same hook, two hooks, two routes의 coordinator가 동일 policy를 사용
- coordinator는 hook-local singleton이 아니라 runtime/scope 수명의 registry-owned
service이며 scope generation 전환에서 신규 admission을 닫고 late commit을 fence
- `SUPERSEDE`는 이미 server로 보낸 non-replayable command를 cancel/rollback했다고
가정하지 않음
- local serialization은 server idempotency/concurrency authority가 아님
- scope/generation change는 pending result commit을 fence
### 14. Optimistic patch
snapshot 전체 restore만 사용하지 않는다.
```text
OptimisticLayer
mutationId
logicalKey
commandIdentityToken
baseCacheRevision
expectedEntityRevision | null
patch
inversePatch
affectedQueryDefinitions
```
선택 가능한 구현:
- cache entry revision CAS
- ordered optimistic layer log
- operation-specific compare-and-apply patch
공통:
1. affected exact queries cancel
2. bounded current revision/value 확인
3. registered pure patch 적용
4. other mutation layer와 ordering 보존
5. failure에서 자기 layer만 제거/역적용
6. success result/revision과 reconcile
7. invalidation/refetch
old whole snapshot을 복원해 다른 mutation commit을 덮지 않는다.
optimistic update를 하지 않는 조건:
- snapshot/patch/result byte ceiling 초과
- cache entry missing/wrong revision
- non-deterministic merge
- high-conflict command
- scope/generation transition
- unknown effect certainty
그 경우 pending UX만 보여 주고 server response/refetch를 기다린다.
### 15. Effect certainty와 conflict
```text
MutationEffect
NOT_APPLIED
COMMITTED
UNKNOWN
```
- timeout/cancel/network failure가 `NOT_APPLIED`를 자동 의미하지 않음
- keyed backend status/receipt가 있어야 ambiguous command reconcile 가능
- `UNKNOWN`은 새 idempotency key로 자동 retry 금지
- 409 business conflict, REST 412, GraphQL safe conflict code, gRPC `ABORTED`
common conflict surface로 mapping하되 의미 차이는 feature policy가 소유
- server revision/merge/overwrite decision은 cache가 아니라 use case 소유
### 16. Mutation success, seed와 invalidation
server commit 뒤:
- exact returned result를 schema/mapper/fence/budget 검증
- registered detail seed policy가 있으면 exact current entity/revision만 write
- list/aggregate는 default invalidate
- list patch는 deterministic sort/filter/membership policy가 있을 때만
- invalidation topic은 query key가 아닌 opaque registry identity
- current mutation lease 동안 remote hints coalesce
local invalidation failure는 committed command를 failure로 바꾸지 않는다.
cache health를 degraded로 기록하고 bounded authoritative refetch를 예약한다.
### 17. Cross-context
현재 cross-tab wire는 invalidate-only다. 유지한다.
- query data/key/input/cursor/validator를 broadcast하지 않음
- remote event는 authority가 아니라 stale hint
- account/scope/version 검증은 VD-13
- mutation ordering/optimistic layer를 tab 간 복제하지 않음
- sequence gap은 모든 registered namespace를 stale 처리하되 active query만
bounded refetch한다. inactive query는 다음 mount/focus에서 revalidate하고,
persistence가 선택된 경우 durable ledger refresh는 VD-13 절차를 따른다.
### 18. GraphQL과 normalized cache
persisted GraphQL query도 mapped operation result를 TanStack에 cache한다.
- query key는 semantic application input
- GraphQL document/hash는 key/value에 없음
- GraphQL SDK cache는 `no-cache`/disabled
- partial data default reject
- approved partial result는 completeness metadata를 application contract가 소유
- missing/error field를 previous complete value와 자동 merge하지 않음
normalized entity cache가 필요하면:
- bounded context 하나가 TanStack operation cache를 대체
- key fields/typename, eviction, pagination merge, optimistic layer, logout/scope,
persistence와 removal을 별도 ADR
- dual write/read 금지
현재 `NOT_SELECTED`다.
### 19. Connect/gRPC-Web server stream
ordinary Query는 terminal operation 결과를 전제로 한다.
- unary는 normal query 가능
- finite server stream을 complete aggregate로 쓸 경우 staging buffer에 bounded
accumulate하고 valid Connect EndStream 또는 gRPC-Web terminal status,
schema/mapper/fence 뒤 atomic cache commit
- long-running stream은 `ServerStreamPort`와 registered reducer/invalidation owner
- frame마다 query cache를 append하여 unbounded event history를 만들지 않음
- stream reconnect를 Query retry로 하지 않음
- gap/overflow는 current snapshot 폐기 또는 authoritative query refetch
### 20. Persistence, SSR와 offline
- memory cache runtime은 `COMPOSED`
- IndexedDB query persister reference는 VD-13 기준
`DESIGNED_NOT_IMPLEMENTED`
- product persistence는 `NOT_SELECTED`
- SSR hydration은 `NOT_SELECTED`
- offline mutation queue는 `NOT_SELECTED`
VD-25 profile은 persistence를 직접 켜지 않는다. joined VD-13
scope/persistence profile, reference runtime과 제품 allowlist/retention/scope가
모두 구현·선택된 query만 IndexedDB persistence를 사용할 수 있다.
server-state policy를 이유로 Web Storage에 query payload를 넣지 않는다.
### 21. Security와 privacy
- authorization result를 staleTime/cache hit으로 대체하지 않음
- account/logout transition에서 sensitive data를 즉시 fence/hide
- query key에 raw PII/account/resource ID/URL/document/message 금지
- cache value에 credential/header/validator/trace/error raw data 금지
- optimistic layer에도 command token/raw body를 저장하지 않음
- command identity token/logical key를 diagnostics, cross-context wire나 persistence에
넣지 않음
- developer tools/diagnostics production exposure policy
- cache poisoning 방지를 위해 schema/mapper/result contract와 generation 검증
- cross-context event에 data 없음
### 22. Observability
허용:
- query/mutation definition/profile ID
- hit/miss/stale/fresh/refresh/evict outcome
- result item/estimated-byte/page bucket
- runtime identity intern entry/canonical-byte/active-lease bucket
- focus/reconnect/invalidation refetch reason
- mutation concurrency/duplicate/optimistic/rollback/conflict/effect bucket
- invalidation/seed/degraded recovery outcome
- scope/profile version의 low-cardinality bucket
금지:
- query key/input/value
- command identity token/logical key
- identity intern canonical bytes/token actual value
- cursor/snapshot/validator/revision actual value
- resource/account/tenant ID
- GraphQL/protobuf/REST DTO
- optimistic patch/snapshot
### 23. Testing
query definition/key:
- definition/input/key/executor type/runtime binding
- VD-13↔VD-25 topic set/version exact join과 bounded many-to-many fan-out
- runtime identity token same-input stability, random-token collision regeneration과
bounded failure의 cache/join 0회
- intern entry/total-byte ceiling, active non-eviction, Query GC/mutation terminal
lease release와 runtime-close leak 0
- undefined/NaN/Date/class/accessor/cycle/sparse/oversize collision fixture
- same semantic input stable identity
- protocol wire identity 변화가 key에 들어가지 않음
- per-profile stale/gc/refetch/retry owner
cache admission:
- mapped immutable plain value only
- item/estimated byte cap
- schema/mapper/generation failure write 0
- auth/contract failure stale visibility
mutation:
- same semantic command identity, same logical key의 distinct input
- same hook/two hooks/two routes
- runtime/scope coordinator의 logical-key serial/parallel과
join/reject/independent duplicate 결과
- out-of-order success/failure
- optimistic layer/CAS rollback without overwriting other commit
- effect `NOT_APPLIED/COMMITTED/UNKNOWN`
- server response detail seed + list invalidate
- invalidation failure after commit
- account switch during pending command
pagination:
- null/repeated/cyclic cursor
- single-page cursor identity-token collision/isolation과 memory-only enforcement
- `hasMore`/`nextCursor` 불일치와 snapshot drift
- snapshot change
- max page/item/byte
- page eviction/refetch
- duplicate identity/revision policy
- cancellation and late page
integration:
- REST/GraphQL/gRPC unary → schema → mapper → cache → UI
- focus/reconnect/offline/stale-degraded
- conditional 304 exact binding
- cross-tab invalidation/mutation lease
- logout/account/release generation
- finite stream atomic commit and overflow
### 24. Rollout
1. VD-13 strict key codec/scope snapshot interface를 확정한다.
2. current `useApplicationQuery({queryKey, execute})` 뒤에 bound definition adapter를
추가한다.
3. reference queries를 shadow key/policy로 비교하되 secondary cache write 금지.
4. per-profile policy/result ceiling을 read query에 canary한다.
5. typed mutation definition과 input-aware coordinator를 도입한다.
6. optimistic layer/CAS를 low-conflict command에만 canary한다.
7. cursor page reference vertical을 구현한다.
8. arbitrary key/executor와 raw optimistic callback API를 제거한다.
9. account/generation browser evidence와 runbook drill 뒤 traffic을 올린다.
rollback:
- 신규 query/mutation admission/optimistic patch를 kill switch로 닫음
- current scope queries cancel
- unsafe/incompatible memory cache clear
- pending command effect certainty reconcile
- current basic facade 또는 no-optimistic authoritative refetch로 downgrade
- schema/mapper/query definition/backend artifact를 coherent set으로 복구
## 규범 기준
- [TanStack Query v5 Important Defaults](https://tanstack.com/query/v5/docs/framework/react/guides/important-defaults)
- [TanStack Query v5 Query Cancellation](https://tanstack.com/query/v5/docs/framework/react/guides/query-cancellation)
## 완료 기준
- caller가 arbitrary key/executor/TanStack option을 조합할 수 없다.
- strict key codec과 VD-13 scope projection이 모든 query에 적용된다.
- VD-13 normative key layout과 scope/persistence profile을 재정의하지 않고 exact
join한다.
- mapped/bounded/current-generation value만 cache에 들어간다.
- transport와 Query retry owner가 중복되지 않는다.
- cursor page가 next/snapshot/loop/page/item/byte ceiling을 갖는다.
- runtime-private exact equality guard까지 통과한 command identity만 declared
join되고 distinct input이 같은 Promise로 잘못 join되지 않는다.
- concurrent optimistic rollback이 다른 committed update를 덮지 않는다.
- effect certainty, conflict, seed와 invalidation owner가 operation별로 닫힌다.
- GraphQL normalized dual cache와 unbounded stream cache가 없다.
- scope/logout/provider fault와 rollback/removal evidence가 통과한다.