Files
suaveplan/openspec/changes/complete-storage-uri-adapters/tasks.md
2026-05-27 23:29:53 +01:00

10 KiB

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=<offset>-<end> 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>/<path>, zone mismatch raises STORAGE_CDN_BUNNY_URI.ZONE_MISMATCH
  • 4.3 src/uri-plugin/uri-plugin.ts — pull-zone parse: bunnycdn+pull://<pull-zone>/<path>; 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 .mds 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=<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 .mds
  • 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/<pkg>
  • 7.3 bunx turbo test:coverage --filter=@suaveplan/<pkg> (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/<pkg>
  • 7.8 Dependent sweep — bunx turbo test --filter=...@suaveplan/<pkg> 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/<adapter>/spec.md into openspec/specs/<adapter>/spec.md (one per adapter)
  • 8.4 Move openspec/changes/complete-storage-uri-adapters/ → openspec/archive/<yyyy-mm-dd>-complete-storage-uri-adapters/