# Capability Spec Delta: storage-cdn-rackspace (complete-storage-uri-adapters) **Package**: `@suaveplan/storage-cdn-rackspace` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: `rackspace://` URI plugin factory The package SHALL export `rackspaceUriPlugin(options)` from `./uri-plugin/uri-plugin` returning a `UriPlugin` whose `scheme` is `"rackspace"` and whose `adapter` is the supplied `RackspaceCdnAdapter`. The factory SHALL validate `options.container` is a non-empty string; failure raises `StorageCdnRackspaceUriError(STORAGE_CDN_RACKSPACE_URI.INVALID_URI)` synchronously. #### Scenario: factory returns a UriPlugin with scheme rackspace - **WHEN** `rackspaceUriPlugin({ adapter, container: "assets" })` is called - **THEN** the returned plugin's `scheme === "rackspace"`, `adapter` is the same reference, and `runUriPluginConformance` passes #### Scenario: empty container is rejected - **WHEN** `rackspaceUriPlugin({ adapter, container: "" })` is called - **THEN** the call throws with `code === "STORAGE_CDN_RACKSPACE_URI.INVALID_URI"` **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"exports a UriPlugin with scheme rackspace"` AND `src/uri-plugin/uri-plugin.test.ts::"rejects empty container"` --- ### Requirement: Container + object-key parsing The plugin SHALL parse `rackspace:///` URIs by taking the authority as the container and the path (minus leading `/`) as the object key. The literal authority `_` (or empty) SHALL rebind to the factory's configured `container`. A mismatched non-`_` container SHALL raise `STORAGE_CDN_RACKSPACE_URI.CONTAINER_MISMATCH`. #### Scenario: matching container parses successfully - **WHEN** `plugin.parse("rackspace://assets/img/a.jpg")` is called against a plugin with `container: "assets"` - **THEN** the returned `key === "img/a.jpg"` and `meta.container === "assets"` #### Scenario: underscore authority rebinds to configured container - **WHEN** `plugin.parse("rackspace://_/img/a.jpg")` is called against the same plugin - **THEN** the returned `key === "img/a.jpg"` and `meta.container === "assets"` #### Scenario: mismatched container raises CONTAINER_MISMATCH - **WHEN** `plugin.parse("rackspace://wrong/img/a.jpg")` is called - **THEN** the call throws with `code === "STORAGE_CDN_RACKSPACE_URI.CONTAINER_MISMATCH"` **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"parses matching container"` AND `src/uri-plugin/uri-plugin.test.ts::"underscore rebinds to configured container"` AND `src/uri-plugin/uri-plugin.test.ts::"raises CONTAINER_MISMATCH for wrong container"` --- ### Requirement: Auth-token threading without URI exposure The plugin SHALL NOT carry the Rackspace auth token in URIs. Authentication lives entirely on the bound `RackspaceAuthClient` / `RackspaceCdnAdapter`. When the adapter's token has expired and refresh fails, the plugin SHALL re-wrap the failure as `STORAGE_CDN_RACKSPACE_URI.AUTH_TOKEN_EXPIRED` preserving the original on `cause`. #### Scenario: token expiry on adapter becomes AUTH_TOKEN_EXPIRED - **WHEN** the bound adapter raises a token-expired error during a routed read - **THEN** the resolver's caller sees `StorageCdnRackspaceUriError` with `code === "STORAGE_CDN_RACKSPACE_URI.AUTH_TOKEN_EXPIRED"` and `cause` set #### Scenario: no auth token appears in parsed URI - **WHEN** `plugin.parse("rackspace://assets/img/a.jpg")` is called - **THEN** the returned `meta` does NOT include `X-Auth-Token` or any `auth` key **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"wraps token expiry as AUTH_TOKEN_EXPIRED"` AND `src/uri-plugin/uri-plugin.test.ts::"never exposes auth token on parsed meta"` --- ### Requirement: TempURL signed-URL verification The plugin SHALL verify `?temp_url_sig=&temp_url_expires=` query parameters when present, using the existing `buildTempUrlSignature` helper against the configured `tempUrlKey` on the bound adapter. Mismatched signatures SHALL raise `STORAGE_CDN_RACKSPACE_URI.TEMP_URL_INVALID`. #### Scenario: valid temp-URL passes verification - **WHEN** `plugin.parse("rackspace://assets/x?temp_url_sig=&temp_url_expires=99")` is called against a plugin whose adapter's `tempUrlKey` produces the same signature - **THEN** the parse succeeds and the resolver routes the read normally #### Scenario: invalid temp-URL raises TEMP_URL_INVALID - **WHEN** `plugin.parse("rackspace://assets/x?temp_url_sig=bogus&temp_url_expires=99")` is called - **THEN** the call throws with `code === "STORAGE_CDN_RACKSPACE_URI.TEMP_URL_INVALID"` **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"accepts a valid temp-URL signature"` AND `src/uri-plugin/uri-plugin.test.ts::"raises TEMP_URL_INVALID on bad signature"` --- ### Requirement: `build(key, meta?)` with optional region echo The plugin SHALL implement `build(key, meta?)` returning `rackspace:///`. When `meta?.region` is set, the result SHALL append `?region=` so downstream resolvers can re-bind regions per URI. Unsupported region values SHALL raise `STORAGE_CDN_RACKSPACE_URI.REGION_UNSUPPORTED`. #### Scenario: build without region omits query string - **WHEN** `plugin.build("img/a.jpg")` is called against a plugin with `container: "assets"` - **THEN** the result is `"rackspace://assets/img/a.jpg"` #### Scenario: build with valid region appends query - **WHEN** `plugin.build("img/a.jpg", { region: "iad" })` is called - **THEN** the result is `"rackspace://assets/img/a.jpg?region=iad"` #### Scenario: unsupported region raises REGION_UNSUPPORTED - **WHEN** `plugin.build("x", { region: "mars" })` is called - **THEN** the call throws with `code === "STORAGE_CDN_RACKSPACE_URI.REGION_UNSUPPORTED"` **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"build omits region when absent"` AND `src/uri-plugin/uri-plugin.test.ts::"build appends valid region"` AND `src/uri-plugin/uri-plugin.test.ts::"raises REGION_UNSUPPORTED for invalid region"` --- ### Requirement: Span-attribute redaction The plugin SHALL redact `temp_url_sig`, `temp_url_expires`, and `X-Auth-Token` from any URI surfaced on OTel span attributes or in raised error `.uri` discriminants. Redaction SHALL be exposed as `redactSignedUrl(uri): string`. #### Scenario: temp-URL parameters are redacted on span attribute - **WHEN** the resolver routes `read("rackspace://assets/x?temp_url_sig=abc&temp_url_expires=99")` and emits a span - **THEN** the `cdn.uri` attribute is `"rackspace://assets/x?temp_url_sig=&temp_url_expires="` **Verified by:** `src/uri-plugin/redact-signed-url.test.ts::"redacts temp_url and auth-token parameters"` --- ### Requirement: `STORAGE_CDN_RACKSPACE_URI_CODES` registry The package SHALL export `STORAGE_CDN_RACKSPACE_URI_CODES` produced by `defineCodes("STORAGE_CDN_RACKSPACE_URI", [...] as const)` covering exactly 6 codes: `INVALID_URI`, `CONTAINER_MISMATCH`, `REGION_UNSUPPORTED`, `TEMP_URL_INVALID`, `AUTH_TOKEN_EXPIRED`, `KEY_TOO_LONG`. Every code SHALL be reachable, and `StorageCdnRackspaceUriError` SHALL extend `SuaveplanError` with `uri`/`scheme` discriminants. #### Scenario: every code is reachable - **WHEN** the test suite exercises every documented failure path - **THEN** every member of `STORAGE_CDN_RACKSPACE_URI_CODES` appears on at least one raised error instance **Verified by:** `src/uri-plugin/error-codes.test.ts::"exposes 6 codes all reachable"` --- ### Requirement: AbortSignal threading The plugin SHALL forward `AbortSignal` unchanged to every adapter call. A pre-aborted signal SHALL short-circuit with the adapter's existing `STORAGE_CDN_RACKSPACE.ABORTED` code without invoking the adapter. #### Scenario: pre-aborted signal short-circuits - **WHEN** the resolver routes `read("rackspace://assets/x", AbortSignal.abort())` - **THEN** the rejection has `code === "STORAGE_CDN_RACKSPACE.ABORTED"` and the adapter is NEVER invoked **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"short-circuits on pre-aborted signal"` --- ### Requirement: Universal-tier purity Every source file under `src/uri-plugin/` SHALL be universal-tier: ZERO imports from `node:*`, `bun:*`, browser-specific globals, or React. #### Scenario: tier-purity grep returns empty - **WHEN** `grep -rnE "from ['\"](node:|bun:)" packages/storage/storage-cdn-rackspace/src/uri-plugin/` is run - **THEN** the command returns zero matches **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"uri-plugin module has no node: or bun: imports"` --- ### Requirement: Subpath exports The package SHALL declare subpath exports for `"./uri-plugin/uri-plugin"`, `"./uri-plugin/redact-signed-url"`, and `"./uri-plugin/error-codes"`. #### Scenario: subpath imports resolve - **WHEN** `import("@suaveplan/storage-cdn-rackspace/uri-plugin/uri-plugin")` is executed - **THEN** the module resolves and re-exports `rackspaceUriPlugin` **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"is importable via the documented subpath"` --- ### Requirement: Conformance harness pass `rackspaceUriPlugin` SHALL pass `runUriPluginConformance` with fixtures covering container-match cases, underscore-rebind cases, container-mismatch failures, temp-URL forms, and a non-empty `buildRoundTrip` set. #### Scenario: conformance fixtures pass - **WHEN** `runUriPluginConformance(rackspaceUriPlugin(opts), fixtures)` is invoked with the documented fixture set - **THEN** the call returns without throwing **Verified by:** `src/uri-plugin/uri-plugin.test.ts::"passes runUriPluginConformance"`