Initial Commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Pass `SuaveplanError` subclasses through `StorageUriResolver` unchanged
|
||||
|
||||
**Package**: `@suaveplan/storage-uri` (behavioural change), `@suaveplan/drive-uri` (spec tightening only — no code change)
|
||||
**Status**: PROPOSED
|
||||
**Type**: behaviour fix
|
||||
**Depends on**: `add-storage-uri` (in `openspec/changes/`), `add-drive-uri` (in `openspec/changes/`) — neither archived yet, so both deltas update in-place.
|
||||
|
||||
- **Spec delta (storage-uri)**: ./specs/storage-uri/spec.md
|
||||
- **Spec delta (drive-uri)**: ./specs/drive-uri/spec.md
|
||||
|
||||
## Why
|
||||
|
||||
`StorageUriResolver` today re-wraps **every** non-`StorageUriError` rejection from a plugin's adapter as `STORAGE_URI.ADAPTER_ERROR` (or `STORAGE_URI.WRITE_UNSUPPORTED` for write throws). That was a reasonable default at the time of `add-storage-uri` — the resolver had no knowledge of any other error class — but it breaks the error-code discriminator system for every downstream plugin family that emits `SuaveplanError` subclasses.
|
||||
|
||||
Concretely: a `drive+mesh://` URI failing chunk-signature verification today reaches the consumer as `STORAGE_URI.ADAPTER_ERROR` with `cause.code === "DRIVE_URI.MESH_VERIFY_FAILED"` (or, transitively, `cause.cause.code === "DRIVE_MESH.SIG_VERIFY_FAILED"`). Consumers cannot write `switch (err.code)` against the meaningful code without inspecting `.cause` chains — defeating the whole point of having a per-package code registry. The same problem applies to every `DriveUriError`, every `StorageHttpError`, every `StorageS3Error`, and any future URI-plugin family.
|
||||
|
||||
The fix is local: teach the resolver to treat any `SuaveplanError` subclass as already-namespaced and pass it through untouched. Wrap only **non-Suaveplan** throwables (`Error`, plain objects, strings, etc.) — those still need the `STORAGE_URI.ADAPTER_ERROR` envelope for stable diagnostics. `StorageUriError` itself remains passed through (no double-wrap), unchanged from today.
|
||||
|
||||
This change also retires the spec deviations that `add-drive-uri` carried at the top of its delta ("the resolver wraps `DriveUriError` as `STORAGE_URI.ADAPTER_ERROR`, preserving the original on `cause`"). With the pass-through in place, the drive-uri scenarios can assert directly on `err.code === "DRIVE_URI.*"` without `.cause` indirection.
|
||||
|
||||
## What changes
|
||||
|
||||
- MODIFIED: `StorageUriResolver`'s error-wrap branch — change the existing `instanceof StorageUriError ? rethrow : wrap` discriminator to `instanceof SuaveplanError ? rethrow : wrap`. `StorageUriError extends SuaveplanError` so the existing `StorageUriError` pass-through is preserved as a special case of the new rule.
|
||||
- MODIFIED: The `WRITE_UNSUPPORTED` re-wrap branch (for `write()` failures from the adapter) — same discriminator change. `SuaveplanError` subclasses thrown from `adapter.set` pass through with their original code; plain `Error` throws are still wrapped as `STORAGE_URI.WRITE_UNSUPPORTED`.
|
||||
- MODIFIED: `storage-uri` spec delta — two requirements updated:
|
||||
- "Adapter error re-wrapping": broaden the pass-through rule from `StorageUriError` to `SuaveplanError`; add a scenario asserting that a thrown `DriveUriError` reaches the caller with its original `code`.
|
||||
- "Streaming writes with buffered fallback": broaden the same rule on the write path; the "read-only adapter surfaces `WRITE_UNSUPPORTED`" scenario now applies only to non-Suaveplan throwables. A new scenario covers the pass-through case.
|
||||
- MODIFIED: `drive-uri` spec delta — five scenarios tightened to drop the `.cause` indirection. The two `**Note —** revisited in `fix-storage-uri-error-passthrough`...` annotations at the top of the drive-uri delta are removed (the first one); the second note (about signal threading) stays.
|
||||
- ADDED: A new `storage-uri` test `src/resolver/resolver.passthrough.test.ts` (or extension of `resolver.test.ts`) covering:
|
||||
- A thrown `SuaveplanError("FOO.BAR", ...)` passes through unchanged.
|
||||
- A thrown plain `Error("disk fault")` is still wrapped as `STORAGE_URI.ADAPTER_ERROR` with `cause.message === "disk fault"`.
|
||||
- A thrown `SuaveplanError` from `adapter.set` passes through (no `WRITE_UNSUPPORTED` wrap).
|
||||
- MODIFIED: Existing `drive-uri` tests that assert on `err.cause.code` — switched to assert on `err.code` directly. The drive-uri test suite shrinks slightly in line count (no more `.cause` ladders) and stays at 100/100/100/100 coverage.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Signal threading through `adapter.get`.** Separate concern, tracked under a future `add-signal-to-storage-like` change. Touches every `StorageLike` adapter; defer until a real consumer requires cancellable buffered reads. The signal-threading note in `add-drive-uri/specs/drive-uri/spec.md` stays.
|
||||
- **Resolver-side translation of well-known downstream codes.** Tempting to map `DRIVE_MESH.SIG_VERIFY_FAILED` → some `STORAGE_URI.INTEGRITY_FAILED` umbrella code; rejected because it would force the resolver to know every downstream registry. The pass-through gives consumers the right code without the resolver having to translate.
|
||||
- **`UriResolverLike` in `@suaveplan/types`.** No concrete second consumer; deferred until one appears.
|
||||
- **Touching any other URI-plugin package.** This is a one-file behaviour change inside `storage-uri` plus spec tightening in `storage-uri` and `drive-uri`. Other plugin packages (`storage-http`, `storage-cdn-*`, etc.) inherit the new behaviour for free without code change.
|
||||
|
||||
## Source mapping
|
||||
|
||||
This is a follow-up to genesis-original changes; no upstream `suaveplan/` reference.
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| Pass-through trigger is `instanceof SuaveplanError`, not a list of known classes | The `defineCodes` discipline is the contract for "this is a structured, namespaced error"; `SuaveplanError` is its runtime carrier. Any current or future package that uses `defineCodes` automatically participates. Adding a class allowlist would force the resolver to grow whenever a new URI-plugin family lands. |
|
||||
| Plain `Error` / non-`SuaveplanError` throws still wrap | Unstructured throws have no namespaced code; without the wrap, consumers cannot route diagnostics by code at all. The wrap remains the right default for foreign throwables. |
|
||||
| Spec tightening lives in this change, not as a retroactive edit to `add-drive-uri` | Both `add-storage-uri` and `add-drive-uri` are still in `openspec/changes/` (not archived). The cleanest history is: each change captures the contract as authored; this change layers a `MODIFIED` delta on top. Once all three archive, the canonical specs reflect the final behaviour. |
|
||||
|
||||
## Verification approach
|
||||
|
||||
Every requirement carries a `**Verified by:**` line in the respective spec delta. The two new / tightened test files:
|
||||
|
||||
- `src/resolver/resolver.test.ts` — extended with two new `it(...)` blocks (`"passes through thrown SuaveplanError subclasses unchanged"`, `"still wraps non-SuaveplanError throwables as ADAPTER_ERROR"`).
|
||||
- `src/resolver/resolver.writes.test.ts` — extended with one new `it(...)` block (`"passes through thrown SuaveplanError subclasses on write"`).
|
||||
- `packages/drive/drive-uri/src/plugins/**/*.test.ts` — `.cause`-based assertions rewritten to assert on `err.code` directly. No new tests added; same scenarios, simpler assertions.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/storage-uri` to enumerate.
|
||||
|
||||
- `@suaveplan/storage-uri` — [x] tests run / [x] docs reviewed / [ ] no action needed (this is the package being modified)
|
||||
- `@suaveplan/drive-uri` — [x] tests run (assertions tightened) / [x] docs reviewed (spec deviations retired) / [ ] no action needed
|
||||
- `@suaveplan/storage-file` — [ ] tests run / [ ] docs reviewed / [x] no action needed (does not throw `SuaveplanError` subclasses from `StorageLike` methods today; new pass-through path is a no-op for it)
|
||||
- `@suaveplan/storage-http` — [ ] tests run / [ ] docs reviewed / [x] no action needed (throws `StorageHttpError extends SuaveplanError` — automatically benefits from pass-through but no breaking change since old behaviour was `ADAPTER_ERROR{cause:StorageHttpError}` which consumers were inspecting via `.cause` already; spec scenario in storage-uri's `WRITE_UNSUPPORTED` requirement is the only place this surfaced)
|
||||
- `@suaveplan/storage-cdn`, `storage-cdn-rackspace`, `storage-cdn-bunnycdn` — [ ] tests run / [ ] docs reviewed / [x] no action needed (same as storage-http — `SuaveplanError` subclasses pass through; downstream tests will be more direct but currently pass via `.cause` inspection, which still works)
|
||||
|
||||
The `[ ] tests run` boxes for the four storage adapter packages are unchecked because the pass-through is purely permissive — consumers who were inspecting `.cause` see the same `cause` populated (the wrap still happens for plain `Error`, and `SuaveplanError` subclasses now reach the caller directly which is strictly more information). Run `bunx turbo test --filter=...@suaveplan/storage-uri` before merging to confirm no consumer relied on the wrap envelope itself (`err.code === "STORAGE_URI.ADAPTER_ERROR"`).
|
||||
|
||||
### Affected upstreams
|
||||
|
||||
- `@suaveplan/error` — None. `SuaveplanError` is consumed as-is via `instanceof`.
|
||||
- `@suaveplan/types` — None.
|
||||
|
||||
### Behaviour change propagation (§13a)
|
||||
|
||||
**Old behaviour:** `resolver.read(uri)` rejects with `StorageUriError{code: "STORAGE_URI.ADAPTER_ERROR", cause: downstreamErr}` when the plugin's adapter throws.
|
||||
|
||||
**New behaviour:** `resolver.read(uri)` rejects with `downstreamErr` directly (same instance) when `downstreamErr instanceof SuaveplanError`; otherwise still rejects with `StorageUriError{code: "STORAGE_URI.ADAPTER_ERROR", cause: downstreamErr}`.
|
||||
|
||||
**Consumer search command:**
|
||||
|
||||
```bash
|
||||
grep -rln 'STORAGE_URI\.ADAPTER_ERROR\|"STORAGE_URI.ADAPTER_ERROR"' packages/ \
|
||||
| grep -v "/dist/\|/coverage/\|/storage-uri/"
|
||||
```
|
||||
|
||||
**Recommended replacement:** consumers that currently `switch (err.cause?.code)` to route on the downstream code can now `switch (err.code)` directly. No migration is forced — the old `.cause` inspection still works (the wrap still happens for non-`SuaveplanError`).
|
||||
@@ -0,0 +1,75 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: drive-uri (fix-storage-uri-error-passthrough)
|
||||
|
||||
**Package**: `@suaveplan/drive-uri`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement carries a `**Verified by:**` line at authoring time. This delta MODIFIES five scenarios across three requirements from `add-drive-uri` (still in `openspec/changes/`, not yet archived). The `**Note —** revisited in `fix-storage-uri-error-passthrough`...` annotation at the top of the drive-uri `add-drive-uri` delta is also retired by this change. (The companion `**Note —** buffered-read signal threading` annotation stays, since `add-signal-to-storage-like` has not landed.)
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: `drive://` plugin parses authority as driveId and delegates to `Drive.read`
|
||||
|
||||
The `driveUriPlugin(options)` factory SHALL return a `UriPlugin` with `scheme === "drive"`. Its `parse` SHALL extract the URI authority as `driveId` and the remainder as a slash-prefix-trimmed `key`. The returned plugin SHALL forward `key` to the consumer-supplied `Drive` via `Drive.read(key, { signal })` (or `Drive.createReadStream` on `open()`). The plugin SHALL throw `DRIVE_URI.DRIVE_ID_MISMATCH` when the URI's driveId does not equal the `driveId` declared in `options`.
|
||||
|
||||
The `StorageUriResolver` SHALL surface this throw directly per the pass-through rule (see `fix-storage-uri-error-passthrough` spec delta for storage-uri).
|
||||
|
||||
#### Scenario: URI dispatches to Drive.read
|
||||
|
||||
- **WHEN** `resolver.read("drive://primary/folder/file.bin")` is called and the resolver holds `driveUriPlugin({ drive, driveId: "primary" })`
|
||||
- **THEN** `drive.read("folder/file.bin", { signal })` is invoked exactly once and its return value is the resolver's result
|
||||
|
||||
#### Scenario: driveId mismatch surfaces DRIVE_URI.DRIVE_ID_MISMATCH directly
|
||||
|
||||
- **WHEN** `resolver.read("drive://other/x")` is called and the plugin is configured with `driveId: "primary"`
|
||||
- **THEN** the call rejects with `DriveUriError` having `code === "DRIVE_URI.DRIVE_ID_MISMATCH"`, `uri === "drive://other/x"`, `expected === "primary"`, `actual === "other"` (the resolver passes the `DriveUriError` through unchanged per the storage-uri pass-through rule; no `STORAGE_URI.*` envelope is added)
|
||||
|
||||
#### Scenario: build round-trips driveId and key
|
||||
|
||||
- **WHEN** `plugin.build("folder/file.bin", { driveId: "primary" })` is called
|
||||
- **THEN** the returned string equals `"drive://primary/folder/file.bin"`
|
||||
|
||||
**Verified by:** `src/plugins/drive/drive.test.ts::"dispatches a drive:// URI to Drive.read"` AND `src/plugins/drive/drive.test.ts::"throws DRIVE_ID_MISMATCH when URI driveId differs from configured driveId"` AND `src/plugins/drive/drive.test.ts::"build round-trips driveId and key into a drive:// URI"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Template fetch and hydration failures map to distinct DRIVE_URI codes
|
||||
|
||||
When `options.templateProvider.fetchTemplate` rejects, the plugin SHALL re-wrap the rejection as `DRIVE_URI.TEMPLATE_NOT_AVAILABLE` with the original error preserved on `cause`. When `Drive.hydrateFrom` rejects (any `DTP_*` code), the plugin SHALL re-wrap the rejection as `DRIVE_URI.TEMPLATE_INVALID` with the original on `cause`. The two failures MUST NOT be conflated — operator response differs (network/availability vs. integrity).
|
||||
|
||||
The `StorageUriResolver` SHALL surface both `DriveUriError`s directly per the pass-through rule.
|
||||
|
||||
#### Scenario: fetchTemplate rejection becomes TEMPLATE_NOT_AVAILABLE
|
||||
|
||||
- **WHEN** `resolver.read("drive+template://templA/x")` is called and `templateProvider.fetchTemplate` rejects with `new Error("404")`
|
||||
- **THEN** the call rejects with `DriveUriError` having `code === "DRIVE_URI.TEMPLATE_NOT_AVAILABLE"` AND `cause.message === "404"`
|
||||
|
||||
#### Scenario: hydrateFrom rejection becomes TEMPLATE_INVALID
|
||||
|
||||
- **WHEN** `resolver.read("drive+template://templA/x")` is called and `drive.hydrateFrom` rejects with a `SuaveplanError` carrying `code === "DTP.TEMPLATE_EXPIRED"`
|
||||
- **THEN** the call rejects with `DriveUriError` having `code === "DRIVE_URI.TEMPLATE_INVALID"` AND `cause.code === "DTP.TEMPLATE_EXPIRED"`
|
||||
|
||||
**Verified by:** `src/plugins/drive-template/drive-template.test.ts::"wraps fetchTemplate rejection as TEMPLATE_NOT_AVAILABLE"` AND `src/plugins/drive-template/drive-template.test.ts::"wraps hydrateFrom rejection as TEMPLATE_INVALID"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Writes via drive:// schemes are rejected
|
||||
|
||||
The three plugins SHALL reject `set` (write) calls with `DRIVE_URI.WRITE_UNSUPPORTED`. The rejection SHALL carry the original URI and scheme on the error so operators can see misuse rate via `drive_uri.writes_total{scheme, outcome: "error"}`.
|
||||
|
||||
The `StorageUriResolver` SHALL surface this throw directly per the pass-through rule — `err.code === "DRIVE_URI.WRITE_UNSUPPORTED"` reaches the caller without a `STORAGE_URI.WRITE_UNSUPPORTED` envelope.
|
||||
|
||||
#### Scenario: write against drive:// rejects with DRIVE_URI.WRITE_UNSUPPORTED
|
||||
|
||||
- **WHEN** `resolver.write("drive://primary/x", new Uint8Array([1, 2, 3]))` is called
|
||||
- **THEN** the call rejects with `DriveUriError` having `code === "DRIVE_URI.WRITE_UNSUPPORTED"`, `uri === "drive://primary/x"`, `scheme === "drive"`
|
||||
|
||||
#### Scenario: writes_total counter increments on the rejection
|
||||
|
||||
- **WHEN** `resolver.write("drive+mesh://swarmA/x", bytes)` is called
|
||||
- **THEN** `drive_uri.writes_total{scheme: "drive+mesh", outcome: "error"}` is incremented by 1
|
||||
|
||||
**Verified by:** `src/plugins/drive/drive.test.ts::"rejects writes with DRIVE_URI.WRITE_UNSUPPORTED"` AND `src/otel/otel.test.ts::"increments writes_total on rejected writes"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
@@ -0,0 +1,65 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# 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"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
@@ -0,0 +1,62 @@
|
||||
# Tasks — fix-storage-uri-error-passthrough
|
||||
|
||||
> Every checkbox MUST reflect work actually done in this change.
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [ ] 1.1 Read `openspec/changes/add-storage-uri/specs/storage-uri/spec.md` requirements "Adapter error re-wrapping" and "Streaming writes with buffered fallback"
|
||||
- [ ] 1.2 Read `openspec/changes/add-drive-uri/specs/drive-uri/spec.md` — confirm the wrap-aware scenarios to tighten
|
||||
- [ ] 1.3 Read `openspec/AGENTS.md` §0c, §2c, §13, §13a, §14
|
||||
- [ ] 1.4 Read `packages/core/error/src/error.ts` (or equivalent) — confirm `SuaveplanError` is the right `instanceof` target
|
||||
- [ ] 1.5 Draft `proposal.md`, `specs/storage-uri/spec.md` (MODIFIED form), `specs/drive-uri/spec.md` (MODIFIED form), `tasks.md`
|
||||
- [ ] 1.6 Build numbered Requirement/Scenario implementation checklist (§0c step 6) — 2 MODIFIED storage-uri requirements, 3 MODIFIED drive-uri requirements
|
||||
|
||||
## 2. Storage-uri implementation
|
||||
|
||||
- [ ] 2.1 Locate the resolver's error-handling branch — `packages/storage/storage-uri/src/resolver/resolver.ts` (`#wrapAdapterError` or equivalent)
|
||||
- [ ] 2.2 Change the discriminator from `err instanceof StorageUriError` to `err instanceof SuaveplanError`
|
||||
- [ ] 2.3 Same change in the write path — `packages/storage/storage-uri/src/resolver/resolver.writes.ts` (`WRITE_UNSUPPORTED` wrap branch)
|
||||
- [ ] 2.4 Verify the `import` is from `@suaveplan/error` (it already is for `StorageUriError`'s base — `SuaveplanError` ships from there)
|
||||
- [ ] 2.5 Update sibling `.md` docs — `resolver.md`, `resolver.writes.md` — to describe the new pass-through behaviour (§4a doc-drift gate)
|
||||
- [ ] 2.6 Update README "Features" section if it claims errors are uniformly wrapped
|
||||
|
||||
## 3. Storage-uri tests
|
||||
|
||||
- [ ] 3.1 `src/resolver/resolver.test.ts` — rename existing `"wraps adapter errors as ADAPTER_ERROR"` to `"wraps non-SuaveplanError throwables as ADAPTER_ERROR"`; assertion stays (plain `Error` thrown → `STORAGE_URI.ADAPTER_ERROR` wrap)
|
||||
- [ ] 3.2 `src/resolver/resolver.test.ts` — add `it("passes through thrown SuaveplanError subclasses unchanged", ...)` — instantiate a `SuaveplanError("FOO.BAR", ...)` directly (no need to import `DriveUriError`), throw from a fake adapter, assert resolver re-throws same instance with `err.code === "FOO.BAR"`
|
||||
- [ ] 3.3 `src/resolver/resolver.writes.test.ts` — rename existing `"wraps read-only adapter error as WRITE_UNSUPPORTED"` to `"wraps non-Suaveplan throw on set as WRITE_UNSUPPORTED"` and switch the fixture to throw a plain `new Error(...)` (not a `StorageHttpError`); assertion stays
|
||||
- [ ] 3.4 `src/resolver/resolver.writes.test.ts` — add `it("passes through thrown SuaveplanError subclasses on write", ...)` — fake adapter throws `new SuaveplanError("FOO.BAR", ...)` from `set`, assert resolver re-throws same instance
|
||||
- [ ] 3.5 Every `it(...)` carries `{ timeout: 5000 }`
|
||||
- [ ] 3.6 No `vi.mock`; tests construct real fake adapters per CLAUDE.md rule 2
|
||||
- [ ] 3.7 All test imports remain via `@suaveplan/testing/runner` (rule 11)
|
||||
|
||||
## 4. Drive-uri test tightening (no production code change)
|
||||
|
||||
- [ ] 4.1 `packages/drive/drive-uri/src/plugins/drive/drive.test.ts` — switch the `DRIVE_ID_MISMATCH` test from asserting `err.cause.code === "DRIVE_URI.DRIVE_ID_MISMATCH"` to asserting `err.code === "DRIVE_URI.DRIVE_ID_MISMATCH"` (and `err instanceof DriveUriError`)
|
||||
- [ ] 4.2 `packages/drive/drive-uri/src/plugins/drive/drive.test.ts` — switch the `WRITE_UNSUPPORTED` test to direct-code assertion
|
||||
- [ ] 4.3 `packages/drive/drive-uri/src/plugins/drive-template/drive-template.test.ts` — switch `TEMPLATE_NOT_AVAILABLE` and `TEMPLATE_INVALID` tests to direct-code assertion
|
||||
- [ ] 4.4 `packages/drive/drive-uri/src/plugins/drive-mesh/drive-mesh.test.ts` — same treatment if a `.cause`-based assertion exists
|
||||
- [ ] 4.5 Coverage stays at 100/100/100/100 (no production code changed; some branches in the test helpers may become unreachable — remove unused helpers if so per §7a — write the test against real behaviour, don't preserve dead code)
|
||||
|
||||
## 5. Spec annotation cleanup (drive-uri)
|
||||
|
||||
- [ ] 5.1 In `openspec/changes/add-drive-uri/specs/drive-uri/spec.md`, remove the top-of-file `> **Note — error-wrap behaviour is revisited in fix-storage-uri-error-passthrough.**` paragraph (the behaviour it described is now retired by this change)
|
||||
- [ ] 5.2 Keep the second top-of-file `> **Note — buffered-read signal threading is revisited in add-signal-to-storage-like.**` paragraph as-is (separate concern, not retired yet)
|
||||
- [ ] 5.3 Confirm no remaining scenarios in `add-drive-uri/specs/drive-uri/spec.md` reference `STORAGE_URI.ADAPTER_ERROR` or "cause.code" indirection for drive-uri throws — if any remain, tighten them in this delta
|
||||
|
||||
## 6. Gates
|
||||
|
||||
- [ ] 6.1 `bunx turbo lint typecheck test --filter=@suaveplan/storage-uri`
|
||||
- [ ] 6.2 `bunx turbo test:coverage --filter=@suaveplan/storage-uri --force` — 100/100/100/100 Istanbul
|
||||
- [ ] 6.3 `bunx turbo lint typecheck test --filter=@suaveplan/drive-uri`
|
||||
- [ ] 6.4 `bunx turbo test:coverage --filter=@suaveplan/drive-uri --force` — 100/100/100/100 Istanbul
|
||||
- [ ] 6.5 `bunx turbo build --filter=@suaveplan/storage-uri @suaveplan/drive-uri`
|
||||
- [ ] 6.6 Dependent sweep — `bunx turbo test --filter=...@suaveplan/storage-uri` (catches any consumer that asserted on `err.code === "STORAGE_URI.ADAPTER_ERROR"` for what is now a passed-through `SuaveplanError`)
|
||||
- [ ] 6.7 `bun scripts/lint/check-test-runner-imports.ts`
|
||||
- [ ] 6.8 Banned-pattern scan empty for both packages' `src/`
|
||||
- [ ] 6.9 Spec-reconciliation gate — every modified scenario's `**Verified by:**` test name exists verbatim
|
||||
|
||||
## 7. Manifest & archival (defer)
|
||||
|
||||
- [ ] 7.1 This change folder remains in `openspec/changes/` until `add-storage-uri` and `add-drive-uri` are archived together. The fold step for all three changes happens as a single archival wave so the canonical `openspec/specs/storage-uri/spec.md` and `openspec/specs/drive-uri/spec.md` reflect the final, pass-through behaviour from day one.
|
||||
- [ ] 7.2 At archive time, the modified scenarios from this delta supersede the originals in the `add-storage-uri` / `add-drive-uri` deltas during the fold.
|
||||
Reference in New Issue
Block a user