# Tasks — complete-storage-uri-adapters > Every checkbox MUST reflect work actually done in this change. ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## 0. Pre-flight - [ ] 0.1 Read `openspec/AGENTS.md` §0c, §13, §14 - [ ] 0.2 Read `openspec/archive/2026-05-16-add-storage-uri/proposal.md`, `tasks.md`, and `specs/storage-uri/spec.md` — confirm the `UriPlugin` contract and the `runUriPluginConformance` harness requirements - [ ] 0.3 Read `packages/storage/storage-uri/src/types/plugin.types.ts` (`UriPlugin`, `ParsedUri`, `StreamingStorageExtensions`, `StatResult`) and `packages/storage/storage-uri/src/conformance/conformance.ts` (`runUriPluginConformance`, `UriPluginFixtures`) verbatim - [ ] 0.4 Read each of the four adapter packages' `index.ts` to map the exported surface the plugin must wrap - [ ] 0.5 Confirm no adapter already ships a `uri-plugin` (verified by `find packages/storage/storage-{file,http,cdn-bunnycdn,cdn-rackspace} -name 'uri-plugin*'` returning empty) - [ ] 0.6 Build the numbered Requirement/Scenario implementation checklist (per §0c step 6) — one per adapter, plus shared scaffolding row ## 1. Shared scaffolding - [ ] 1.1 Confirm `@suaveplan/storage-uri` is on `workspace:^` in each of the four adapter packages' `devDependencies` (for the conformance test only — no runtime dep) - [ ] 1.2 Add a co-located `redact-signed-url/redact-signed-url.ts` helper to `@suaveplan/storage-http` (used by URI redaction policy) and re-use the same module from `bunnycdn` and `rackspace` via adapter-local mirrors (do NOT cross-import — each package keeps its own redaction list per scheme) ## 2. `@suaveplan/storage-file` — `fileUriPlugin` - [ ] 2.1 `src/uri-plugin/uri-plugin.ts` — `fileUriPlugin(options)` factory; scheme `"file"`; `parse(uri)` with RFC 8089 authority handling, Windows drive-letter detection (`/^[A-Za-z]:/` after stripping leading `/`), and `..` traversal rejection against `rootDir` - [ ] 2.2 `src/uri-plugin/uri-plugin.ts` — `build(key)` inverse with RFC 3986 segment-percent-encoding and Windows drive-letter preservation - [ ] 2.3 `src/uri-plugin/error-codes.ts` — `defineCodes("STORAGE_FILE_URI", [...])` with the 6 codes; `StorageFileUriError extends SuaveplanError` - [ ] 2.4 `src/uri-plugin/uri-plugin.md` (≥200 words, basic + advanced) - [ ] 2.5 `src/uri-plugin/error-codes.md` - [ ] 2.6 `src/uri-plugin/uri-plugin.test.ts` — POSIX paths, Windows drive-letter, relative-vs-absolute, `..` escape, permission-denied wrap, conformance via `runUriPluginConformance` with `validUris` (POSIX + Windows + `file://localhost/...`), `invalidUris` (`file:`, `file://host/x`, `file:///../../etc/passwd`), and `buildRoundTrip` pairs - [ ] 2.7 Add `"./uri-plugin/uri-plugin"` subpath export + `"./uri-plugin/error-codes"` to `package.json` - [ ] 2.8 Add named re-exports in `src/index.ts` (explicit — no `export *`, per CLAUDE.md rule 10) - [ ] 2.9 Update `README.md` `## Modules` with the new entries ## 3. `@suaveplan/storage-http` — `httpUriPlugin` + `httpsUriPlugin` - [ ] 3.1 `src/uri-plugin/uri-plugin.ts` — internal `buildHttpUriPlugin` builder; `httpUriPlugin(options)` and `httpsUriPlugin(options)` factories differing only in `scheme` - [ ] 3.2 `src/uri-plugin/uri-plugin.ts` — full RFC 3986 parse via `parseUri`, key = `path + (query ? "?" + query : "")` so presigned query strings survive - [ ] 3.3 `src/uri-plugin/uri-plugin.ts` — range parsing via `?range=bytes=-` query parameter; on parse failure raise `STORAGE_HTTP_URI.RANGE_INVALID`; on success route to `adapter.getPartial(key, { offset, length })` - [ ] 3.4 `src/uri-plugin/uri-plugin.ts` — redirect-loop classification (re-wrap `STORAGE_HTTP.REDIRECT_LOOP` if/when adapter raises; fallback maps any `Response.status === 310` or fetch loop signal) - [ ] 3.5 `src/uri-plugin/uri-plugin.ts` — `build(key, meta?)` inverse via `new URL(key, adapter.options.baseUrl)` with `scheme` substitution for the `http`/`https` factory split - [ ] 3.6 `src/uri-plugin/redact-signed-url.ts` — case-insensitive deny-list strip; exported for reuse and unit-tested - [ ] 3.7 `src/uri-plugin/error-codes.ts` — `defineCodes("STORAGE_HTTP_URI", [...])` with 6 codes; `StorageHttpUriError` - [ ] 3.8 `src/uri-plugin/uri-plugin.md`, `redact-signed-url.md`, `error-codes.md` - [ ] 3.9 `src/uri-plugin/uri-plugin.test.ts` — happy `http://` + `https://` round-trips, query-string preservation, range parsing success + failure, redirect loop wrap, redaction unit tests, full `runUriPluginConformance` per factory - [ ] 3.10 Subpath exports in `package.json` for the three new files - [ ] 3.11 Named re-exports in `src/index.ts` - [ ] 3.12 README modules update ## 4. `@suaveplan/storage-cdn-bunnycdn` — `bunnyUriPlugin` - [ ] 4.1 `src/uri-plugin/uri-plugin.ts` — `bunnyUriPlugin(options)` factory; ONE plugin registered against TWO schemes (`bunnycdn` and `bunnycdn+pull`) via the same factory returning one of two `UriPlugin` instances (the resolver registers each explicitly) - [ ] 4.2 `src/uri-plugin/uri-plugin.ts` — storage-zone parse: `bunnycdn:///`, zone mismatch raises `STORAGE_CDN_BUNNY_URI.ZONE_MISMATCH` - [ ] 4.3 `src/uri-plugin/uri-plugin.ts` — pull-zone parse: `bunnycdn+pull:///`; `set`/`delete` reject with `UNSUPPORTED_OPERATION` - [ ] 4.4 `src/uri-plugin/uri-plugin.ts` — signed-URL handling: parse `?token=...&expires=...`, forward to existing `buildTokenAuthQuery` helper. Token bytes NEVER persisted on `meta` - [ ] 4.5 `src/uri-plugin/uri-plugin.ts` — `build(key, meta?)` per surface (`storage` vs `pull` based on `meta.surface`) - [ ] 4.6 `src/uri-plugin/redact-signed-url.ts` — strips `token`, `signature`, `expires` - [ ] 4.7 `src/uri-plugin/error-codes.ts` — `defineCodes("STORAGE_CDN_BUNNY_URI", [...])` with 6 codes - [ ] 4.8 Co-located `.md`s for every implementation file - [ ] 4.9 `src/uri-plugin/uri-plugin.test.ts` — zone match/mismatch, pull-zone read-only enforcement, signed-URL passthrough, redaction, full conformance harness - [ ] 4.10 Subpath exports + named re-exports + README ## 5. `@suaveplan/storage-cdn-rackspace` — `rackspaceUriPlugin` - [ ] 5.1 `src/uri-plugin/uri-plugin.ts` — `rackspaceUriPlugin(options)` factory; scheme `"rackspace"`; authority = container, path = object key - [ ] 5.2 `src/uri-plugin/uri-plugin.ts` — authority `_` or empty rebinds to configured container; mismatched container raises `CONTAINER_MISMATCH` - [ ] 5.3 `src/uri-plugin/uri-plugin.ts` — temp-URL signed-URL verification via existing `buildTempUrlSignature`, on mismatch raise `TEMP_URL_INVALID` - [ ] 5.4 `src/uri-plugin/uri-plugin.ts` — `build(key, meta?)` with optional `?region=` echo - [ ] 5.5 `src/uri-plugin/redact-signed-url.ts` — strips `temp_url_sig`, `temp_url_expires`, `X-Auth-Token` - [ ] 5.6 `src/uri-plugin/error-codes.ts` — `defineCodes("STORAGE_CDN_RACKSPACE_URI", [...])` with 6 codes - [ ] 5.7 Co-located `.md`s - [ ] 5.8 `src/uri-plugin/uri-plugin.test.ts` — container match/mismatch, auth-token threading, temp-URL valid/invalid, region echo, redaction, conformance - [ ] 5.9 Subpath exports + named re-exports + README ## 6. Cross-package conformance - [ ] 6.1 Each adapter's `uri-plugin.test.ts` MUST import `runUriPluginConformance` from `@suaveplan/storage-uri/conformance/conformance` and call it with a fixture set covering valid/invalid/round-trip - [ ] 6.2 Add a cross-scheme integration test in `packages/storage/storage-uri/tests/integration/all-four-schemes.test.ts` composing all four plugins behind one resolver and asserting end-to-end dispatch - [ ] 6.3 Tier purity: `grep -rnE "from ['\"](node:|bun:)" packages/storage/storage-cdn-*/src/uri-plugin/` returns empty (CDN plugins are universal-tier); `storage-file/src/uri-plugin/` MAY use `node:` (server-tier adapter); `storage-http/src/uri-plugin/` MUST be universal - [ ] 6.4 Test-runner imports — every new `.test.ts` imports from `@suaveplan/testing/runner` only (rule 11) ## 7. Gates (per adapter package) For each of `storage-file`, `storage-http`, `storage-cdn-bunnycdn`, `storage-cdn-rackspace`: - [ ] 7.1 `bun run check` - [ ] 7.2 `bunx turbo lint typecheck test --filter=@suaveplan/` - [ ] 7.3 `bunx turbo test:coverage --filter=@suaveplan/` (100/100/100/100 istanbul) - [ ] 7.4 Banned-pattern scan returns zero - [ ] 7.5 Missing-`.md` scan returns zero - [ ] 7.6 Subpath export count matches feature module count - [ ] 7.7 `bun scripts/openspec-conformance.ts @suaveplan/` - [ ] 7.8 Dependent sweep — `bunx turbo test --filter=...@suaveplan/` green - [ ] 7.9 Cross-package: full `bunx turbo test --filter=@suaveplan/storage-uri` green after the four plugins land ## 8. Manifest & archival - [ ] 8.1 `bun scripts/folder-reorg/generate-manifest.ts` to refresh `MANIFEST.md` (export-graph change) - [ ] 8.2 `bunx markdownlint-cli2 --fix` on touched files - [ ] 8.3 Fold each `specs//spec.md` into `openspec/specs//spec.md` (one per adapter) - [ ] 8.4 Move `openspec/changes/complete-storage-uri-adapters/` → `openspec/archive/-complete-storage-uri-adapters/`