# Tasks — storage-http > Every checkbox MUST reflect work actually done in this change. ## 1. Spec authoring - [ ] 1.1 Read `openspec/specs/storage/spec.md` and confirm capability boundary - [ ] 1.2 Read `openspec/AGENTS.md` §0c, §13, §14 - [ ] 1.3 Read `openspec/archive/2026-05-12-storage-file/` end-to-end as the closest precedent - [ ] 1.4 Read `openspec/archive/2026-04-24-enterprise-storage-s3/` for network-backed adapter precedent (OTel + retry classification) - [ ] 1.5 Draft `proposal.md` from `openspec/templates/proposal.md` - [ ] 1.6 Draft `design.md` covering file layout, status→code map, range fallback, ETag cache, uploader contract, tier purity - [ ] 1.7 Draft `specs/storage-http/spec.md` from `openspec/templates/spec-delta.md` - [ ] 1.8 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete planned `it(...)` titles - [ ] 1.9 Build the numbered Requirement/Scenario implementation checklist (per §0c step 6) ## 2. Scaffolding - [ ] 2.1 Create `packages/storage/storage-http/` mirroring `packages/storage/storage-file/` 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/retry`, `@suaveplan/types`, `@opentelemetry/api`, `zod`, `tslib` (all `workspace:^` / `catalog:`) - [ ] 2.4 Wire devDeps: `@suaveplan/testing`, `@vitest/coverage-istanbul`, `fast-check`, `mitata`, `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.ts` — `defineCodes("STORAGE_HTTP", [...])` with 10 codes, `StorageHttpError` class, `key`/`httpStatus`/`url` discriminants - [ ] 3.2 `src/types/http-storage.types.ts` — `HttpStorageOptions`, `FetchLike`, `HttpUploader`, `EtagCacheOptions` interfaces - [ ] 3.3 `src/types/http-storage.schemas.ts` — `HttpStorageOptionsSchema` (Zod) with `baseUrl` http(s) + trailing-slash validation - [ ] 3.4 `src/fetch-adapter/fetch-adapter.ts` — `defaultFetch()` wrapping `globalThis.fetch`, `AbortSignal` propagation, network-reject → `NETWORK_ERROR` - [ ] 3.5 `src/key-sanitizer/key-sanitizer.ts` — `sanitizeKey` strict boundary + default per-segment `encodeURIComponent` - [ ] 3.6 `src/range/range.ts` — `Range` header builder + 206/200/416 disambiguation - [ ] 3.7 `src/etag-cache/etag-cache.ts` — in-memory LRU keyed by sanitised key - [ ] 3.8 `src/uploader/uploader.ts` — `HttpUploader` contract + `presignedPutUploader(presigner)` helper - [ ] 3.9 `src/retry-helper/retry-helper.ts` — transient status classifier + `Retry-After` honour via `@suaveplan/retry` - [ ] 3.10 `src/otel/otel.ts` — meter/tracer wiring, `recordOp(op, outcome, statusClass, ms, bytes?)` - [ ] 3.11 `src/http-storage/http-storage.ts` — `HttpStorage extends AbstractStorage` with all abstract methods, capabilities matrix, `READ_ONLY` enforcement, AbortSignal - [ ] 3.12 `src/registry/registry.ts` — `declare module "@suaveplan/storage"` + `registerHttpProvider()` (idempotent) - [ ] 3.13 `src/index.ts` — explicit named re-exports per §6a (no `export *` in any non-root barrel) ## 4. Tests - [ ] 4.1 `src/key-sanitizer/key-sanitizer.test.ts` — valid, traversal, absolute, null bytes, control chars, overlong (>2048), separator normalisation, per-segment encoding - [ ] 4.2 `src/fetch-adapter/fetch-adapter.test.ts` — forwards to `globalThis.fetch`, `AbortSignal` propagation, network-reject → `NETWORK_ERROR` - [ ] 4.3 `src/range/range.test.ts` — 206 verbatim, 200 fallback + counter increment, 416 → `NOT_FOUND` - [ ] 4.4 `src/etag-cache/etag-cache.test.ts` — LRU eviction, 304 returns cached body, 200 replaces entry - [ ] 4.5 `src/uploader/uploader.test.ts` — `presignedPutUploader` round-trip; injected uploader invoked exactly once on `set` - [ ] 4.6 `src/retry-helper/retry-helper.test.ts` — transient classifier, `Retry-After` honour under `FakeClock`, non-retry on 401 - [ ] 4.7 `src/otel/otel.test.ts` — metrics via real in-memory `MeterProvider`; error span via in-memory `SpanExporter` - [ ] 4.8 `src/http-storage/http-storage.test.ts` — GET round-trip, 404 → undefined, HEAD exists/metadata, AbortSignal pre/mid, READ_ONLY on `set`/`list`/`clear`, `set` with uploader invokes `put` once - [ ] 4.9 `src/http-storage/http-storage.spec-conformance.test.ts` — type conformance, capabilities matrix, every `STORAGE_HTTP` code reachable, tier-purity grep - [ ] 4.10 `src/registry/registry.test.ts` — `createStorage({ type: "http", … })` returns `HttpStorage`; idempotent re-registration - [ ] 4.11 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async) - [ ] 4.12 No imports from `vitest` or `bun:test` in test files — all via `@suaveplan/testing/runner` (§7) ## 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 composition recipe: `storage-http` behind `storage-file` `NamespacedStorage` for cross-process disk cache ## 6. Gates - [ ] 6.1 `bun run check` - [ ] 6.2 `bunx turbo lint typecheck test --filter=@suaveplan/storage-http` - [ ] 6.3 `bunx turbo test:coverage --filter=@suaveplan/storage-http` (100/100/100/100 Istanbul) - [ ] 6.4 `bun scripts/lint/check-test-runner-imports.ts` (rule 11) - [ ] 6.5 Tier-purity grep — `grep -rnE "from ['\"](node:|bun:)" packages/storage/storage-http/src/` returns empty - [ ] 6.6 `bun scripts/openspec-conformance.ts @suaveplan/storage-http` ## 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 Fold `specs/storage-http/spec.md` into `openspec/specs/storage-http/spec.md` - [ ] 7.4 Move `openspec/changes/add-storage-http/` → `openspec/archive/-add-storage-http/`