97 lines
3.6 KiB
Markdown
97 lines
3.6 KiB
Markdown
# Spec-First Inventory — add-cache-storage-redis-backends
|
|
|
|
Per `openspec/AGENTS.md` §0c step 6 + AGENTS.md §2b. Implementation may not begin until this exists.
|
|
|
|
## cache-storage — Requirements
|
|
|
|
### R1. StorageCache extends AbstractCache<T>
|
|
- [ ] R1.S1 instance is `instanceof AbstractCache` and has the full `AbstractCacheShape`
|
|
- [ ] R1.S2 inherited wrap caches the loader result on a miss
|
|
|
|
### R2. 32-byte fixed-width metadata header
|
|
- [ ] R2.S1 header.encode → header.decode round-trips every field
|
|
- [ ] R2.S2 decodeHeader throws ENTRY_CORRUPT on bytes shorter than HEADER_BYTES
|
|
|
|
### R3. TTL expiration is lazy on read
|
|
- [ ] R3.S1 get returns undefined and deletes when entry has expired
|
|
- [ ] R3.S2 entry with no ttl never expires
|
|
|
|
### R4. ENTRY_CORRUPT on short blob from storage
|
|
- [ ] R4.S1 short blob surfaces as ENTRY_CORRUPT with `error.key === originalKey`
|
|
|
|
### R5. Codec<T> decouples T from byte storage
|
|
- [ ] R5.S1 JsonCodec round-trips structured value
|
|
- [ ] R5.S2 JsonCodec.decode throws CODEC_ERROR on malformed bytes
|
|
- [ ] R5.S3 IdentityCodec encode/decode preserves bytes
|
|
|
|
### R6. CACHE_STORAGE error registry
|
|
- [ ] R6.S1 every CACHE_STORAGE_* code reachable
|
|
|
|
### R7. Module augmentation registers "storage" provider
|
|
- [ ] R7.S1 hasCacheProvider("storage") true after import
|
|
- [ ] R7.S2 createCache("storage", ...) returns a StorageCache
|
|
|
|
### R8. Namespacing prefixes every stored key
|
|
- [ ] R8.S1 namespace prefixes keys before delegating to storage
|
|
|
|
### R9. Lifecycle events emitted when EventBusLike provided
|
|
- [ ] R9.S1 emits hit on get after set
|
|
- [ ] R9.S2 emits miss on get of unknown key
|
|
|
|
### R10. Stats counters via getStats
|
|
- [ ] R10.S1 hitRate computes hits / (hits + misses)
|
|
|
|
### R11. Tier purity — universal
|
|
- [ ] R11.S1 src contains no node:/bun: imports
|
|
|
|
## cache-storage — error-code reachability (R6)
|
|
- [ ] ENTRY_CORRUPT — short blob OR decodeHeader on undersized buffer
|
|
- [ ] CODEC_ERROR — JsonCodec.decode malformed bytes
|
|
- [ ] BACKEND_ERROR — underlying storage throws non-cache error → wrapped
|
|
- [ ] KEY_TOO_LONG — set with key longer than max allowed
|
|
- [ ] VALUE_TOO_LARGE — set with value larger than max allowed
|
|
|
|
## cache-redis — Requirements
|
|
|
|
### R1. RedisCache extends AbstractCache<T>
|
|
- [ ] R1.S1 instance is `instanceof AbstractCache` with full shape
|
|
|
|
### R2. RedisLike interface
|
|
- [ ] R2.S1 FakeRedis stub satisfies the interface
|
|
- [ ] R2.S2 cache round-trips set→get against FakeRedis
|
|
|
|
### R3. Native TTL via SET ... EX
|
|
- [ ] R3.S1 positive ttl invokes SET with EX seconds
|
|
- [ ] R3.S2 zero default ttl invokes SET without EX
|
|
|
|
### R4. INVALID_TTL on negative ttl
|
|
- [ ] R4.S1 negative ttl rejected with INVALID_TTL
|
|
|
|
### R5. clear() refuses when no namespace
|
|
- [ ] R5.S1 clear without namespace throws COMMAND_FAILED
|
|
- [ ] R5.S2 clear with namespace deletes only namespaced keys
|
|
|
|
### R6. CACHE_REDIS error registry
|
|
- [ ] R6.S1 every CACHE_REDIS_* code reachable
|
|
|
|
### R7. Codec layer shared with cache-storage
|
|
- [ ] R7.S1 JsonCodec emits UTF-8 string into redis.set
|
|
- [ ] R7.S2 IdentityCodec emits base64 round-trip
|
|
|
|
### R8. Module augmentation registers "redis" provider
|
|
- [ ] R8.S1 hasCacheProvider("redis") true after import
|
|
- [ ] R8.S2 createCache("redis", ...) returns a RedisCache
|
|
|
|
### R9. getStats client-side counters
|
|
- [ ] R9.S1 reports hits/misses/hitRate, size 0
|
|
|
|
### R10. Tier — server
|
|
- [ ] R10.S1 src contains no node:/bun: imports
|
|
|
|
## cache-redis — error-code reachability (R6)
|
|
- [ ] CONNECTION_FAILED — wrap RedisLike error during set/get
|
|
- [ ] COMMAND_FAILED — clear() without namespace
|
|
- [ ] CODEC_ERROR — codec decode failure during get
|
|
- [ ] KEY_TOO_LONG — set with oversized key
|
|
- [ ] INVALID_TTL — negative ttl in set
|