13 KiB
13 KiB
Tasks: complete-storage-s3
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Foundation — constants, error codes, types
- 1.1 Extend
src/constants/index.tswithDEFAULT_MULTIPART_THRESHOLD = 16 MiB,DEFAULT_MULTIPART_CONCURRENCY = 4,MIN_PART_SIZE = 5 MiB,MAX_PART_SIZE = 5 GiB,MAX_PARTS = 10_000,MAX_OBJECT_SIZE = 5 TiB,MAX_TAGS = 10,MAX_PRESIGN_EXPIRES = 7 * 24 * 3600. - 1.2 Expand
S3_CODESviadefineCodes("STORAGE_S3", [...] as const)to the ~50 codes listed indesign.md#error-code-map. Retain the legacy 11 codes verbatim. DocumentMULTIPART_FAILEDas a back-compat alias. - 1.3 Create
src/types/sse.types.ts,acl.types.ts,tagging.types.ts,lifecycle.types.ts,cors.types.ts,conditional.types.ts,checksum.types.ts. Wire intosrc/types/index.ts. - 1.4 Extend
S3Optionsinsrc/types/s3.types.tswithmultipart?: MultipartTuning,transport?: FetchLike,signer?: SigV4Signer,defaultSse?: SseConfig,defaultAcl?: CannedAcl. - 1.5 Create
src/utils/error-mapper.ts— mapsS3ServiceException($metadata.httpStatusCode,name) to the appropriateSTORAGE_S3.*code (412 →PRECONDITION_FAILED, 304 →NOT_MODIFIED, 416 →RANGE_NOT_SATISFIABLE, etc.). Sibling.md+.test.ts.
2. AbstractStorage surface — production-grade
- 2.1
src/core/get/— native Range GET viaRange:header; conditional headers (IfMatch,IfNoneMatch,IfModifiedSince,IfUnmodifiedSince); SSE-C key passthrough;versionIdpassthrough; checksum verification. Sibling.md+.test.ts. - 2.2
src/core/set/— auto-promote-to-multipart branch overmultipart.threshold; single PUT below; SSE / ACL / tagging / metadata / checksum applied at the PUT boundary. Sibling.md+.test.ts. - 2.3
src/core/set-stream/—setStream(key, stream, opts?)that consumesReadableStream<Uint8Array>and routes through the multipart engine without buffering the whole stream. Sibling.md+.test.ts. - 2.4
src/core/set-partial/— branches:offset === 0 && bytes.byteLength > threshold→ multipart engine; otherwisedefaultSetPartial.capabilities.partialWritesdocumented asfalse. Sibling.md+.test.ts. - 2.5
src/core/delete/— versioned-delete viaversionId; conditional delete viaIfMatch. Sibling.md+.test.ts. - 2.6
src/core/head/— full metadata bag (eTag, versionId, lastModified, storageClass, content-*, user-metadata, checksum, SSE algorithm). Sibling.md+.test.ts. - 2.7
src/core/list/—listObjectsV2paginator withprefix,delimiter,maxKeys,continuationToken,startAfter. Sibling.md+.test.ts. - 2.8
src/core/exists/— branch on 404 / 200; documented to use HEAD. Sibling.md+.test.ts.
3. Multipart engine (whole-object writes + UploadPartCopy)
- 3.1
src/multipart/part-chunker.ts+.md+.test.ts— covers exact multiples, ragged final part, empty stream, mid-read abort. - 3.2
src/multipart/worker-pool.ts+.md+.test.ts— bounded-concurrency scheduler with linkedAbortController; covers cap enforcement, first-failure abort-of-peers, caller-abort propagation. - 3.3
src/multipart/multipart-engine.ts+.md+.test.ts— owns init / parts / complete / abort lifecycle; maps each stage failure to its dedicated code. - 3.4
src/multipart/upload-part-copy.ts+.md+.test.ts— server-side multipart copy for objects > 5 GiB. Splits source byCopySourceRange. - 3.5
src/multipart/index.ts— explicit named re-exports only (§6a).
4. S3-specific extensions
- 4.1
src/presign/get-signed-get-url.ts+ sibling docs + tests — TTL + response-content-* + version + SSE forwarding. - 4.2
src/presign/get-signed-put-url.ts+ docs + tests — TTL + content pin + SSE + checksum forwarding. - 4.3
src/presign/get-signed-delete-url.ts+ docs + tests — TTL + version-id support. - 4.4
src/sse/— SSE-S3 / SSE-KMS / SSE-C parameter shaping;keyMd5validation againstMD5(key)at the boundary. - 4.5
src/acl/— canned ACL + grant-policy parameter shaping;getObjectAcl/putObjectAcl. - 4.6
src/copy/—copyObjectwithin-bucket + cross-bucket (viasourceBucket); metadata directive (COPY/REPLACE); tagging directive; conditional headers; SSE override; auto-route toupload-part-copywhen source > 5 GiB. - 4.7
src/tagging/—getObjectTags/putObjectTags/deleteObjectTags; inlinetagsatsettime URL-encoded intox-amz-tagging. - 4.8
src/versioning/—listObjectVersionspaginator;versionIdpassthrough onget/head/delete. - 4.9
src/lifecycle/—get/put/deletebucket lifecycle rules. - 4.10
src/cors/—get/put/deletebucket CORS rules. - 4.11
src/bucket/—createBucket/deleteBucket/bucketExists/getBucketRegion. - 4.12
src/conditional/— shared module that translatesConditionalOptionsinto the right AWS-SDK input fields, used byget/head/delete/copy. - 4.13
src/checksum/— SHA-256 + CRC32C computation + verification on single PUT and onget-with-checksum.
5. Transport / signer / retry plug-points
- 5.1
src/transport/transport.ts+ docs + tests —FetchLikeadapter threaded throughS3ClientFactory.createviarequestHandler/Fetch. - 5.2
src/signer/signer.ts+ docs + tests — pluggableSigV4Signer; defaults to AWS SDK'sSignatureV4. - 5.3
src/retry/retry.ts+ docs + tests — replacewithRetryins3.tswithcreateRetryStrategyfrom@suaveplan/retry. PreserveisRetryableS3Errorclassification (5xx / throttling /SlowDown/RequestTimeout). Delete the oldwithRetryhelper.
6. Top-level S3 class wiring
- 6.1 Refactor
src/s3/s3.tsso it owns construction (validateCredentials,S3Clientbuild with transport/signer plug-points), capability flags (partialReads: true,nativeStreams: true,partialWrites: false), and delegates each surface to its co-located module. KeepS3class ≤ 300 lines. - 6.2 Validate
multipart.partSize ∈ [MIN_PART_SIZE, MAX_PART_SIZE],multipart.threshold ≥ MIN_PART_SIZE,multipart.concurrency ∈ [1, 32]at construction time; throwS3StorageError(MULTIPART_INIT_FAILED, ...). - 6.3 Update
src/index.tsroot barrel to namespace-re-export every new module (export * as multipart from "./multipart/index.js",export * as presign from "./presign/index.js", etc.) per §6a.
7. Subpath exports + package.json
- 7.1 Add subpath exports in
package.jsonfor every feature module undersrc/:./core/get/get,./core/set/set,./core/set-stream/set-stream,./multipart/multipart-engine,./multipart/part-chunker,./multipart/worker-pool,./multipart/upload-part-copy,./presign/get-signed-get-url,./presign/get-signed-put-url,./presign/get-signed-delete-url,./sse/sse,./acl/acl,./copy/copy,./tagging/tagging,./versioning/versioning,./lifecycle/lifecycle,./cors/cors,./bucket/bucket,./conditional/conditional,./checksum/checksum,./transport/transport,./signer/signer,./retry/retry,./utils/error-mapper. - 7.2 Add
@suaveplan/retrytodependenciesif not already present (it is — verifyworkspace:^).
8. Integration tests against Garage (real service, no mocks)
- 8.1
tests/integration/core.test.ts— get / set / delete / list / head round-trips against the Garage fixture. - 8.2
tests/integration/multipart.test.ts— 10 cases per design table (auto-promote, concurrency cap, abort+cleanup, per-stage error codes,setPartialwhole-object, part-size validation). - 8.3
tests/integration/stream.test.ts—setStreamof 25 MiB → 5 parts at 5 MiB; round-trip bytes verify. - 8.4
tests/integration/presign.test.ts— all three presigned URLs; TTL boundary; header pin; response-content-disposition override. - 8.5
tests/integration/sse.test.ts— SSE-S3, SSE-KMS (Garage compatibility), SSE-C with key+MD5 validation; SSE-C mismatch raisesSSE_KEY_MISMATCHbefore request leaves process. - 8.6
tests/integration/acl.test.ts— canned + grants + getObjectAcl + putObjectAcl. - 8.7
tests/integration/copy.test.ts— within-bucket, cross-bucket (second bucket created via fixture extension), > 5 GiB routed via UploadPartCopy (simulated via fixture-level threshold override since a real > 5 GiB object is impractical in CI). - 8.8
tests/integration/tagging.test.ts— inline-at-set, post-write put, get, delete; > 10 tags raisesTAG_LIMIT_EXCEEDED. - 8.9
tests/integration/versioning.test.ts— list versions, get by versionId, delete by versionId. - 8.10
tests/integration/lifecycle.test.ts— round-trip lifecycle rules; delete; get-after-delete raises 404. - 8.11
tests/integration/cors.test.ts— round-trip CORS rules; delete; get-after-delete raises 404. - 8.12
tests/integration/bucket.test.ts— create / exists / region / delete; create-existing raisesBUCKET_ALREADY_EXISTS; delete-non- empty raisesBUCKET_NOT_EMPTY. - 8.13
tests/integration/conditional.test.ts— IfMatch hit → 200; IfMatch miss →PRECONDITION_FAILED; IfNoneMatch / If-*Modified- Since paths. - 8.14
tests/integration/range.test.ts— partial GET via native Range; OOB raisesRANGE_NOT_SATISFIABLE. - 8.15
tests/integration/checksum.test.ts— SHA-256 + CRC32C round trip; deliberately corrupted bytes raiseCHECKSUM_MISMATCH. - 8.16 Extend
@suaveplan/testing-integration-garage-s3fixture surface only as needed (e.g. expose a second-bucket helper for cross-bucket copy; exposeListPartsCommandfor mid-flight assertions). Any extension lands in this change.
9. Docs
- 9.1 Rewrite
README.mdModules + Features sections to cover the full surface (one bullet per module under §3 in this tasks file). - 9.2 Co-located
.mdfor every implementation file per project rule §4 (≥ 200 words, basic + advanced example, API reference, implementation notes). - 9.3 Update
error-codes.mdto enumerate every code with one-sentence semantics + the call site that raises it.
10. Verify (per §13 pre-ship sequence)
- 10.1 Spec reconciliation (§13 step 0): walk every
### Requirementand#### Scenarioinspecs/storage-s3/spec.md; confirm matching impl + verbatim**Verified by:**test names. - 10.2
bunx turbo lint typecheck test --filter=@suaveplan/storage-s3green. - 10.3
bunx turbo test:coverage --filter=@suaveplan/storage-s3100/100/100/100. - 10.4
bunx turbo test:integration --filter=@suaveplan/storage-s3green against the Garage testcontainer. - 10.5 Dependent sweep:
bunx turbo test --filter=...@suaveplan/storage-s3green. - 10.6 Banned-pattern / barrel-shape / runner-import scans (§13 steps 4 / 4a / 4b) — zero output.
- 10.7
bun scripts/openspec-conformance.ts @suaveplan/storage-s3PASS. - 10.8
openspec validate complete-storage-s3 --strictPASS.
11. Archive
- 11.1 Refresh root docs per §13b —
MANIFEST.md,MISSING.yml,STATUS.md,SUMMARY.md; runbunx markdownlint-cli2 --fix. - 11.2 Fold
specs/storage-s3/spec.mdintoopenspec/specs/storage-s3/spec.md(this is the first change to establish that canonical capability file — create the folder underopenspec/specs/only at this archival step). - 11.3 Move
openspec/changes/complete-storage-s3/→openspec/archive/<yyyy-mm-dd>-complete-storage-s3/.