Files
suaveplan/openspec/archive/2026-05-16-fix-storage-uri-error-passthrough/specs/storage-uri/spec.md
2026-05-27 23:29:53 +01:00

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 from add-storage-uri (still in openspec/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 and adapter.get throws new Error("disk fault")
  • THEN the rejection is a StorageUriError with code === "STORAGE_URI.ADAPTER_ERROR", cause.message === "disk fault", uri === "file:///a", and scheme === "file"

Scenario: SuaveplanError subclass passes through with its original code

  • WHEN read("drive://primary/x") is called and adapter.get throws new DriveUriError("DRIVE_ID_MISMATCH", { uri, expected, actual }) (a SuaveplanError subclass with code === "DRIVE_URI.DRIVE_ID_MISMATCH")
  • THEN the rejection is that same DriveUriError instance, err.code === "DRIVE_URI.DRIVE_ID_MISMATCH", and the resolver does NOT wrap it inside a StorageUriError

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.set is 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 where stream yields three chunks
  • THEN adapter.set is 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 whose adapter.set throws new Error("read only mount")
  • THEN the call rejects with StorageUriError having code === "STORAGE_URI.WRITE_UNSUPPORTED" and cause.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 whose adapter.set throws new DriveUriError("WRITE_UNSUPPORTED", { uri, scheme: "drive" })
  • THEN the rejection is that same DriveUriError instance, err.code === "DRIVE_URI.WRITE_UNSUPPORTED", and the resolver does NOT re-wrap it as STORAGE_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"