4.8 KiB
Capability Spec Delta: storage-uri (fix-storage-uri-error-passthrough)
Package: @suaveplan/storage-uri
Status: PROPOSED
Every requirement carries a
**Verified by:**line at authoring time. This delta MODIFIES two requirements fromadd-storage-uri(still inopenspec/changes/, not yet archived).
MODIFIED Requirements
Requirement: Adapter error re-wrapping
Errors thrown by the underlying adapter SHALL be inspected before re-wrap. Any throwable that is an instance of SuaveplanError (the runtime carrier for defineCodes-based error namespaces) SHALL be re-thrown unchanged — the resolver MUST NOT wrap a namespaced error inside its own envelope. Throwables that are NOT SuaveplanError instances (plain Error, foreign objects, strings) SHALL be wrapped in StorageUriError with code === "STORAGE_URI.ADAPTER_ERROR", the original preserved as cause, and uri + scheme discriminants populated.
This includes StorageUriError itself (which extends SuaveplanError) — the pre-existing pass-through is preserved as a special case of the new rule.
Scenario: arbitrary non-Suaveplan throwable is re-wrapped as ADAPTER_ERROR
- WHEN
read("file:///a")is called andadapter.getthrowsnew Error("disk fault") - THEN the rejection is a
StorageUriErrorwithcode === "STORAGE_URI.ADAPTER_ERROR",cause.message === "disk fault",uri === "file:///a", andscheme === "file"
Scenario: SuaveplanError subclass passes through with its original code
- WHEN
read("drive://primary/x")is called andadapter.getthrowsnew DriveUriError("DRIVE_ID_MISMATCH", { uri, expected, actual })(aSuaveplanErrorsubclass withcode === "DRIVE_URI.DRIVE_ID_MISMATCH") - THEN the rejection is that same
DriveUriErrorinstance,err.code === "DRIVE_URI.DRIVE_ID_MISMATCH", and the resolver does NOT wrap it inside aStorageUriError
Scenario: existing StorageUriError is re-thrown unchanged
- WHEN the adapter (via a nested resolver) throws
new StorageUriError("NOT_FOUND", ...) - THEN the same instance is propagated; no re-wrap occurs
Verified by: src/resolver/resolver.test.ts::"wraps non-SuaveplanError throwables as ADAPTER_ERROR" AND src/resolver/resolver.test.ts::"passes through thrown SuaveplanError subclasses unchanged" AND src/resolver/resolver.test.ts::"re-throws StorageUriError unchanged"
Requirement: Streaming writes with buffered fallback
The write(uri, body, signal?) method SHALL accept Uint8Array or ReadableStream<Uint8Array> for body. When the underlying adapter exposes createWriteStream, the resolver SHALL forward it. When the adapter exposes only set, the resolver SHALL drain the stream into a single Uint8Array, call set once, and SHALL increment storage_uri.buffered_write_total{scheme}.
When adapter.set throws, the resolver SHALL apply the same pass-through rule as Adapter error re-wrapping (above): SuaveplanError subclasses SHALL pass through unchanged with their original code; non-SuaveplanError throwables SHALL be wrapped as StorageUriError with code === "STORAGE_URI.WRITE_UNSUPPORTED" and the original preserved as cause.
Scenario: Uint8Array body is forwarded directly
- WHEN
write("file:///a.bin", new Uint8Array([1,2,3]))is called - THEN
adapter.setis invoked exactly once with the exact byte array
Scenario: stream body is drained into a single set call
- WHEN
write("file:///a.bin", stream)is called wherestreamyields three chunks - THEN
adapter.setis invoked exactly once with the concatenated bytes;buffered_write_total{scheme:"file"}is incremented by 1
Scenario: non-Suaveplan throw on set wraps as WRITE_UNSUPPORTED
- WHEN
write("memory://x/y", bytes)is called against a plugin whoseadapter.setthrowsnew Error("read only mount") - THEN the call rejects with
StorageUriErrorhavingcode === "STORAGE_URI.WRITE_UNSUPPORTED"andcause.message === "read only mount"
Scenario: SuaveplanError on set passes through with its original code
- WHEN
write("drive://primary/x", bytes)is called against a plugin whoseadapter.setthrowsnew DriveUriError("WRITE_UNSUPPORTED", { uri, scheme: "drive" }) - THEN the rejection is that same
DriveUriErrorinstance,err.code === "DRIVE_URI.WRITE_UNSUPPORTED", and the resolver does NOT re-wrap it asSTORAGE_URI.WRITE_UNSUPPORTED
Verified by: src/resolver/resolver.writes.test.ts::"forwards Uint8Array body directly to adapter.set" AND src/resolver/resolver.writes.test.ts::"drains ReadableStream into single set call" AND src/resolver/resolver.writes.test.ts::"wraps non-Suaveplan throw on set as WRITE_UNSUPPORTED" AND src/resolver/resolver.writes.test.ts::"passes through thrown SuaveplanError subclasses on write"