12 KiB
12 KiB
Tasks — storage-uri-plugin-bundle
Every checkbox MUST reflect work actually done in this change. Numbered Requirement / Scenario checklist (per AGENTS §0c step 6) is the gating list — implementation is not done until every requirement in
specs/storage-uri-plugin-bundle/spec.mdis ticked off here.
1. Spec authoring
- 1.1 Read
openspec/AGENTS.md§0, §0a, §0b, §0c, §13, §14 - 1.2 Read
openspec/specs/storage-uri/spec.mdend-to-end (UriPlugin contract + conformance) - 1.3 Read
packages/storage/storage-uri/src/(parseUri, error-codes, conformance harness) - 1.4 Read
packages/drive/drive-uri/src/plugins/{drive,drive-mesh,drive-template}/as the 3-plugin reference - 1.5 Read
packages/storage/storage-http/src/types/fetch.types.tsfor theFetchLikere-export pattern - 1.6 Confirm the package is not in
STATUS.md(genesis-original, not pending) - 1.7 Draft
proposal.mdincluding the ≥2-consumer justification - 1.8 Draft
design.mdcovering URI grammar (ABNF), resolution matrix, traversal algorithm, range matrix, tier-purity argument, error-code matrix, file layout - 1.9 Draft
specs/storage-uri-plugin-bundle/spec.mdfrom the OpenSpec delta skeleton - 1.10 Every requirement in the spec delta carries a
**Verified by:**line naming concrete plannedit(...)titles - 1.11 Build the numbered Requirement/Scenario implementation checklist below (§9)
2. Scaffolding
- 2.1 Create
packages/storage/storage-uri-plugin-bundle/mirroringstorage/storage-uri/conventions - 2.2 Write
package.jsonv0.0.0,"sideEffects": false, nine subpath exports perdesign.md §9 - 2.3 Wire runtime deps:
@suaveplan/error,@suaveplan/storage-uri,@suaveplan/types,@opentelemetry/api,zod,tslib(allworkspace:^/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(viacreateTestConfig) - 2.6 Write
tests/setup/polyfills.tsandREADME.mdskeleton - 2.7 Confirm tier purity: zero
node:*,bun:*,window.*imports anywhere undersrc/
3. Implementation
- 3.1
src/error-codes/error-codes.ts—defineCodes("BUNDLE_URI", [...])with 9 codes;BundleUriError extends SuaveplanErrorcarryinguri/scheme/packageSlug/relPath/rootKinddiscriminants - 3.2
src/types/plugin-options.types.ts—BundleUriPluginOptionsinterface (no inline shapes per §11) - 3.3
src/types/plugin-options.schemas.ts—BundleUriPluginOptionsSchema(Zod): slug regex, rootUrl normalisation, optionalcache/fetch/logger/clock - 3.4
src/parse/parse.ts—parse(uri, options),build(key, meta?), traversal-prevention (raw + post-decode + URL-resolve check) - 3.5
src/abort/abort.ts— pre-aborted short-circuit helper raisingBUNDLE_URI.ABORTED; AbortError → BundleUriError conversion preservingcause - 3.6
src/range/range.ts—Rangeheader builder + 206/200/416 disambiguation + offset/length validation - 3.7
src/cache/cache.ts— in-memory LRU keyed by URI, byte-bounded, default-off - 3.8
src/streaming/streaming.ts—createReadStreamforwardingResponse.bodywith one-chunk fallback when body is null - 3.9
src/storage-shim/storage-shim.ts—StorageLikeadapter wrappingFetchLike:get,exists(HEAD),getMetadata(HEAD),getPartial,createReadStream; every write/list/delete/clear throwsREAD_ONLY - 3.10
src/otel/otel.ts— tracer + meter wiring;recordOp(op, rootKind, outcome, ms, bytes?); query-string stripping onbundle.rel_pathattribute; 256-char truncation - 3.11
src/factory/factory.ts—bundleUriPlugin(options)returning theUriPlugin(scheme"bundle", parse, build, adapter = StorageShim) - 3.12
src/index.ts— explicit named re-exports per §6a (noexport *)
4. Tests
- 4.1
src/error-codes/error-codes.test.ts— everyBUNDLE_URI.*code reachable; registry exhaustion - 4.2
src/factory/factory.test.ts— option validation, slug regex, rootUrl trailing-slash enforcement, defaultfetch - 4.3
src/parse/parse.test.ts— accept well-formed URIs; reject scheme mismatch (INVALID_URI), empty rel-path (INVALID_URI), slug mismatch (PACKAGE_MISMATCH), raw..(PATH_TRAVERSAL),%2E%2Edecoded (PATH_TRAVERSAL), URL-resolve escape (PATH_TRAVERSAL);buildround-trips - 4.4
src/storage-shim/storage-shim.test.ts— realfile://temp-dir round-trip viafetch; realnode:httplocalhost fixture round-trip; HEAD exists/metadata; every write/list/delete/clear throwsREAD_ONLY - 4.5
src/range/range.test.ts— 206 verbatim against HTTP fixture; 200 fallback incrementsrange_fallback_total; 416 →RANGE_INVALID; offset/length validation →RANGE_INVALID - 4.6
src/streaming/streaming.test.ts—createReadStreamforwardsResponse.body; one-chunk fallback when body is null - 4.7
src/abort/abort.test.ts— pre-aborted signal →BUNDLE_URI.ABORTED(no fetch); mid-flight abort propagatesAbortErroroncause - 4.8
src/cache/cache.test.ts— off by default; on, evicts oldest at byte ceiling; hit returns same bytes without re-fetch - 4.9
src/otel/otel.test.ts— duration histogram + counters via in-memoryMeterProvider; span attributes via in-memorySpanExporter; query string stripped frombundle.rel_path - 4.10
src/conformance/conformance.test.ts—runUriPluginConformancepasses againstfile://temp-dir root ANDhttp://localhost:<port>/real fixture - 4.11 Every
it(...)carries{ timeout: 5000 }(or 30000 for genuinely async) - 4.12 No imports from
vitestorbun:testin test files — all via@suaveplan/testing/runner(rule 11) - 4.13 No
vi.mock/jest.mock— realnode:httplocalhost fixture + real temp-dirfile://resolution only
5. Documentation
- 5.1 Sibling
.mdfor every implementation.ts(≥200 words, Purpose / Features / Basic + Advanced Examples / API Reference / Implementation Notes) - 5.2
README.mdat package root: description, install, quick-start,## Moduleslinking every co-located.md - 5.3 README documents the per-package registration recipe (
new URL("./", import.meta.url)) - 5.4 README documents the bundler-flatten composition pattern (Vite / esbuild / webpack
?urlimport)
6. Gates
- 6.1
bun run check(biome write+unsafe) - 6.2
bunx turbo lint typecheck test --filter=@suaveplan/storage-uri-plugin-bundle - 6.3
bunx turbo test:coverage --filter=@suaveplan/storage-uri-plugin-bundle— 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:|process)['\"]" packages/storage/storage-uri-plugin-bundle/src/returns empty - 6.6 Banned-pattern scan returns empty (
@deprecated,TODO,FIXME,ts-ignore,ts-expect-error,.skip,.only,any,console.log) - 6.7 Barrel-shape scan (§6a) — no
export *in any featureindex.ts - 6.8 Missing-
.mdscan returns empty - 6.9 Bare-version scan on
package.jsonreturns empty - 6.10 Subpath-export count = feature module count (9)
- 6.11
bun scripts/openspec-conformance.ts @suaveplan/storage-uri-plugin-bundle - 6.12 Mitata benchmarks present at
src/__benchmarks__/bundle-uri.bench.tswith baseline in README
7. Manifest & root docs
- 7.1
bun scripts/folder-reorg/generate-manifest.ts— refreshMANIFEST.md - 7.2
bun scripts/generate-missing.ts— refreshMISSING.yml - 7.3 Update
STATUS.md(genesis-original — append to "Completed" section with the package-summary line) - 7.4 Update
SUMMARY.mdwith the per-package paragraph reflecting the shipped surface - 7.5
bunx markdownlint-cli2 --fixon touched files - 7.6
bunx markdownlint-cli2(no --fix) exits 0
8. Archival
- 8.1 Fold
specs/storage-uri-plugin-bundle/spec.mdintoopenspec/specs/storage-uri-plugin-bundle/spec.md - 8.2 Move
openspec/changes/add-storage-uri-plugin-bundle/→openspec/archive/<yyyy-mm-dd>-add-storage-uri-plugin-bundle/
9. Numbered Requirement / Scenario checklist (§0c step 6 gate)
Implementation is NOT complete until every box below is checked. Each item maps 1:1 to a
### Requirement / #### Scenario in specs/storage-uri-plugin-bundle/spec.md.
- R1 Plugin factory shape and registration
- R1.S1 factory returns a
UriPluginwithscheme: "bundle" - R1.S2 factory validates options via Zod; invalid slug → throws
- R1.S3 rootUrl without trailing slash is normalised (or rejected) deterministically
- R1.S1 factory returns a
- R2
bundle://URI grammar enforced byparse- R2.S1 well-formed URI parses to
{ key, meta: { packageSlug } } - R2.S2 scheme mismatch →
INVALID_URI - R2.S3 empty rel-path →
INVALID_URI - R2.S4 missing authority →
INVALID_URI
- R2.S1 well-formed URI parses to
- R3 Per-package slug enforcement
- R3.S1 URI slug ≠ options.packageSlug →
PACKAGE_MISMATCHcarryingactual
- R3.S1 URI slug ≠ options.packageSlug →
- R4 Traversal prevention (raw + decoded + URL-resolve)
- R4.S1 raw
..segment →PATH_TRAVERSAL - R4.S2 pct-encoded
%2E%2Eafter decode →PATH_TRAVERSAL - R4.S3 URL-resolve outside rootUrl.pathname →
PATH_TRAVERSAL
- R4.S1 raw
- R5 Read-only enforcement on every write surface
- R5.S1
setthrowsREAD_ONLYwithop:"set" - R5.S2
setPartialthrowsREAD_ONLY - R5.S3
deletethrowsREAD_ONLY - R5.S4
clearthrowsREAD_ONLY - R5.S5
listthrowsREAD_ONLY
- R5.S1
- R6 GET resolves through injected
FetchLike- R6.S1 HTTP root: bytes returned verbatim
- R6.S2
file://root: bytes returned verbatim - R6.S3
404→NOT_FOUND - R6.S4 non-2xx non-404 →
FETCH_FAILEDwithhttpStatus
- R7 HEAD-backed
existsandgetMetadata- R7.S1
existsreturnstrueon2xx,falseon404/410 - R7.S2
getMetadataparsescontent-length,content-type,etag,last-modified
- R7.S1
- R8 Range reads
- R8.S1 HTTP root 206 path returns slice
- R8.S2 HTTP root 200 path slices client-side AND increments
range_fallback_total - R8.S3 416 →
RANGE_INVALID - R8.S4 invalid offset/length →
RANGE_INVALID(no fetch) - R8.S5
data:/blob:always slow-pathed, counter incremented
- R9 Streaming reads
- R9.S1
createReadStreamforwardsResponse.bodywhen present - R9.S2 one-chunk fallback when
Response.bodyis null
- R9.S1
- R10 AbortSignal propagation
- R10.S1 pre-aborted signal →
ABORTEDwithout invoking fetch - R10.S2 mid-flight abort propagates underlying
AbortErroroncause
- R10.S1 pre-aborted signal →
- R11 Optional in-memory LRU cache
- R11.S1 cache off by default; counters stay at 0
- R11.S2 cache on; eviction respects
maxBytes - R11.S3 hit returns same bytes without invoking fetch
- R12 OTel instrumentation
- R12.S1
op_duration_msrecorded on success - R12.S2
errors_total{code}recorded on failure - R12.S3 query string stripped from
bundle.rel_pathattribute - R12.S4
bundle.root_kindclassified correctly per resolution-matrix §2
- R12.S1
- R13 Conformance harness pass
- R13.S1
runUriPluginConformancepasses againstfile://root - R13.S2
runUriPluginConformancepasses againsthttp://localhost:<port>/root
- R13.S1
- R14 Error-code registry exhaustion
- R14.S1 every
BUNDLE_URI.*code is reachable from a real call site
- R14.S1 every