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

200 lines
10 KiB
Markdown

# 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 `.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=<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/<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/`