Files
suaveplan/openspec/archive/2026-05-16-add-storage-plugin-cache/tasks.md
2026-05-27 23:29:53 +01:00

8.4 KiB

Tasks — add-storage-plugin-cache

Every checkbox MUST reflect work actually done in this change.

1. Spec authoring

  • 1.1 Read openspec/specs/storage/spec.md and confirm AbstractStorage's 15 abstract methods and the NamespacedStorage decorator precedent
  • 1.2 Read openspec/specs/cache/spec.md and confirm AbstractCache<T> contract (especially whether keys() is part of the canonical surface)
  • 1.3 Read openspec/archive/2026-05-16-add-cache-storage-redis-backends/ for the cache-family conventions
  • 1.4 Read sibling proposal openspec/changes/add-drive-plugin-cache/proposal.md — match option shape, error-swallow rules, OTel naming
  • 1.5 Read openspec/AGENTS.md §0c, §2b, §6a, §7, §11, §13
  • 1.6 Draft proposal.md, design.md, tasks.md, specs/storage-plugin-cache/spec.md
  • 1.7 Every requirement in the spec delta carries a **Verified by:** line naming concrete planned it(...) titles
  • 1.8 Build the numbered Requirement/Scenario implementation checklist (§0c step 6) — 13 requirements, ~30 scenarios

2. Scaffolding

  • 2.1 Create packages/storage/storage-plugin-cache/ mirroring packages/storage/storage-uri/ conventions
  • 2.2 Write package.json v0.0.0, "sideEffects": false, subpath exports for every feature module
  • 2.3 Wire deps: @suaveplan/error, @suaveplan/storage, @suaveplan/cache, @suaveplan/types, @opentelemetry/api, tslib (all workspace:^ / catalog:)
  • 2.4 Wire devDeps: @suaveplan/testing, @suaveplan/storage-file, @suaveplan/storage-http, @suaveplan/cache-storage, @vitest/coverage-istanbul, fast-check, typescript, vite, vite-plugin-dts, vitest
  • 2.5 Write tsconfig.json, tsconfig.lib.json, vite.config.ts, vitest.config.ts (via createTestConfig)
  • 2.6 Write tests/setup/polyfills.ts and README.md skeleton

3. Implementation

  • 3.1 src/error-codes/error-codes.ts — defineCodes("STORAGE_CACHE", ["INVALID_OPTIONS", "INNER_MISSING"] as const), CachedStorageError class, key?/namespace? discriminants
  • 3.2 src/types/options.types.ts — CachedStorageOptions, CachedStorageStats, ResolvedCachedStorageOptions interfaces (no inline shapes per §11)
  • 3.3 src/cached-storage/cached-storage.signal.ts — AbortSignal pre-aborted short-circuit helper
  • 3.4 src/cached-storage/cached-storage.get.ts — get(key, signal?) read-through with hit/miss/populate
  • 3.5 src/cached-storage/cached-storage.write.ts — set(key, value, signal?) and setPartial(...) invalidation
  • 3.6 src/cached-storage/cached-storage.delete.ts — delete(key, signal?) invalidation
  • 3.7 src/cached-storage/cached-storage.clear.ts — clear(signal?) namespaced enumeration + fallback warning
  • 3.8 src/cached-storage/cached-storage.passthrough.ts — exists/list/getMetadata/getWithMetadata/getSize/getItemSize/getPartial/createReadStream/createWriteStream delegates
  • 3.9 src/cached-storage/cached-storage.ts — class CachedStorage extends AbstractStorage assembling the helpers from .get.ts/.write.ts/.delete.ts/.clear.ts/.passthrough.ts/.signal.ts. Constructor validation; cache/inner/getCacheStats() accessors.
  • 3.10 src/factory/factory.ts — cachedStorage(inner, options): CachedStorage factory + synchronous validateOptions(options) throwing INVALID_OPTIONS/INNER_MISSING
  • 3.11 src/otel/otel.ts — meter + tracer wiring, counters hits/misses/populations/invalidations/errors, histogram entry_bytes, span attribute helpers
  • 3.12 src/index.ts — explicit named re-exports per §6a

4. Tests (every it(...) carries { timeout: 5000 }, 30000 for async integration)

  • 4.1 src/error-codes/error-codes.test.ts — both codes reachable from a real call site, discriminants populated
  • 4.2 src/factory/factory.test.ts — factory returns CachedStorage, throws INNER_MISSING on nullish inner, throws INVALID_OPTIONS on missing/malformed cache
  • 4.3 src/cached-storage/cached-storage.test.ts — instance is instanceof AbstractStorage AND instanceof CachedStorage, cache/inner exposed by reference, getCacheStats snapshot semantics
  • 4.4 src/cached-storage/cached-storage.get.test.ts — hit short-circuits inner, custom namespace in key, miss populates, shouldCache gate, 16 MiB ceiling, undefined not cached, cache.get rejection falls through, cache.set rejection swallowed
  • 4.5 src/cached-storage/cached-storage.write.test.ts — set invalidates post-success, failed set does not invalidate, setPartial invalidates whole entry, invalidateOnWrite:false skips, cache.delete rejection swallowed
  • 4.6 src/cached-storage/cached-storage.delete.test.ts — delete invalidates post-success, failed delete does not invalidate, invalidateOnDelete:false skips
  • 4.7 src/cached-storage/cached-storage.clear.test.ts — namespaced enumeration via cache.keys(), warning + skip when keys() absent, invalidateOnClear:false skips
  • 4.8 src/cached-storage/cached-storage.passthrough.test.ts — every passthrough method delegates without touching the cache (one it(...) per method)
  • 4.9 src/cached-storage/cached-storage.signal.test.ts — pre-aborted skips cache work in get, pre-aborted skips invalidation in set
  • 4.10 src/otel/otel.test.ts — counters increment, histogram observed, span outcome attribute correct, errors_total op label populated
  • 4.11 tests/integration/memory-in-front-of-http.test.ts — real MemoryCache + real storage-http (in-process fetch stub): two reads, inner invoked once
  • 4.12 tests/integration/file-cache-survives-restart.test.ts — real StorageCache over real storage-file + in-memory inner: cache survives wrapper re-instantiation
  • 4.13 tests/integration/namespaced-isolation.test.ts — two wrappers / two namespaces / one cache: isolation on get and on clear
  • 4.14 No imports from vitest or bun:test in test files — all via @suaveplan/testing/runner (rule 11)
  • 4.15 No vi.mock / jest.mock; all dependencies are real per CLAUDE.md rule 2

5. Documentation

  • 5.1 Sibling .md for every implementation .ts (≥200 words, basic + advanced examples, full API reference)
  • 5.2 README.md links every module under ## Modules
  • 5.3 README documents the six composition recipes from design.md (memory→S3, OPFS→HTTPS, file→Bunny, Redis→S3, wrap() SWR, compose with storage-uri)
  • 5.4 README documents the relationship to @suaveplan/cache-storage (the inverse direction) and to add-drive-plugin-cache (the drive-tier twin)
  • 5.5 README documents the shouldCache / maxEntryBytes defaults and the rationale (16 MiB matches drive-mesh-protocol's frame ceiling and drive-plugin-cache's ceiling)

6. Gates

  • 6.1 bun run check
  • 6.2 bunx turbo lint typecheck test --filter=@suaveplan/storage-plugin-cache
  • 6.3 bunx turbo test:coverage --filter=@suaveplan/storage-plugin-cache — 100/100/100/100 Istanbul
  • 6.4 bunx turbo build --filter=@suaveplan/storage-plugin-cache
  • 6.5 bun scripts/lint/check-test-runner-imports.ts (rule 11)
  • 6.6 Tier-purity grep — grep -rnE "from ['\"](node:|bun:)" packages/storage/storage-plugin-cache/src/ returns empty
  • 6.7 Banned-pattern scan returns empty (§2)
  • 6.8 Missing-.md scan returns empty (§4)
  • 6.9 Non-root export * scan returns empty (§6a)
  • 6.10 Bare-version scan returns empty (§1)
  • 6.11 Folder-naming scan (§10) — children under cached-storage/ and factory/ do not include the parent's singular form as a suffix
  • 6.12 Spec-reconciliation gate — every ### Requirement is implemented; every **Verified by:** test name exists verbatim (§13 step 0)
  • 6.13 File-size scan — every src/**/*.ts ≤ 300 lines, every *.test.ts ≤ 500 lines

7. Manifest & archival

  • 7.1 Add package to MANIFEST.md via bun scripts/folder-reorg/generate-manifest.ts
  • 7.2 Update STATUS.md (genesis-original — add to "Completed" once shipped)
  • 7.3 Update SUMMARY.md with the one-paragraph description
  • 7.4 bunx markdownlint-cli2 --fix on touched docs
  • 7.5 Fold specs/storage-plugin-cache/spec.md into openspec/specs/storage-plugin-cache/spec.md
  • 7.6 Move openspec/changes/add-storage-plugin-cache/ → openspec/archive/<yyyy-mm-dd>-add-storage-plugin-cache/