# Capability Spec Delta: audio-codecs (audio-codecs) **Package**: `@suaveplan/audio-codecs` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. ## ADDED Requirements ### Requirement: Universal-tier purity The `@suaveplan/audio-codecs` package SHALL import zero browser-only APIs (`window`, `document`, `navigator`, `AudioContext`, `AudioBuffer`) and zero statically-imported Node-only APIs (`node:fs`, `node:path`, `process`, `Buffer`). Filesystem access SHALL be reached only through runtime-gated dynamic `import('node:fs/promises')` inside source-resolution code paths, never as a top-level import. The package's `dependencies` SHALL be limited to `@suaveplan/audio-core`, `@suaveplan/audio-metadata`, `@suaveplan/streams`, `@suaveplan/error`, `@suaveplan/types`, `tslib`, and `zod`; `@opentelemetry/api` is the only permitted non-workspace runtime dependency. WASM codec packages SHALL be declared as `optionalDependencies` and loaded only via dynamic `import()`. #### Scenario: Built dist references no platform globals at module scope - **WHEN** the built `dist/` is grep'd for top-level imports - **THEN** it MUST NOT statically import `window`, `document`, `navigator`, `AudioContext`, `node:fs`, `node:path`, `process`, or `Buffer` — `node:fs/promises` appears only inside a dynamic `import()` expression #### Scenario: Tests pass under both runtimes - **WHEN** the suite runs under `bun test` (server) and under vitest with a `happy-dom` shim (browser) - **THEN** both runs pass with identical results, with `fetch`-backed source resolution exercised in the browser run and `fs`-backed resolution exercised in the server run **Verified by:** `tests/purity.test.ts::"dist statically imports no browser or Node globals"` AND `tests/purity.test.ts::"dependency set is limited to the declared workspace deps and zod"` --- ### Requirement: CodecRegistry plugin management The package SHALL export a `CodecRegistry` class exposing `register(codec)`, `unregister(name)`, `get(name)`, and `detect(bytes)`. `register` SHALL index the codec by its `name`, every entry in `extensions`, and every entry in `mimeTypes`; registering a codec whose `name` is already present SHALL replace the prior codec. `detect(bytes)` SHALL return the `name` of the first registered codec whose `canDecode(bytes)` returns `true`, or `undefined` when no codec matches. `get` for an unregistered name SHALL return `undefined`. #### Scenario: register then detect round-trips a codec - **WHEN** a caller registers a codec and calls `detect` with bytes its `canDecode` accepts - **THEN** `detect` returns that codec's `name` and `get(name)` returns the same codec instance #### Scenario: detect returns undefined for unrecognised bytes - **WHEN** `detect` is called with bytes no registered codec's `canDecode` accepts - **THEN** it returns `undefined` without throwing **Verified by:** `src/registry/registry.test.ts::"register/unregister/get manage codecs by name, extension, and mime type"` AND `src/registry/registry.test.ts::"detect returns the matching codec name or undefined"` --- ### Requirement: AudioCodec plugin contract The package SHALL export an `AudioCodec` interface carrying readonly `name`, `extensions`, and `mimeTypes`, plus `canDecode(bytes)`, `decode(bytes)`, `encode(pcm, opts)`, and the optional `createDecoder()` / `createEncoder()` streaming factories. `decode` SHALL return a `DecodeResult` (`{ channelData: Float32Array[]; sampleRate: number; format: string }`). A codec that does not support encoding SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.NOT_SUPPORTED` from `encode`; a codec that does not support streaming SHALL omit the optional factories rather than returning a non-functional stub. #### Scenario: encode on a decode-only codec throws AUDIO_CODECS.NOT_SUPPORTED - **WHEN** a caller invokes `encode` on a codec whose format is decode-only (e.g. AIFF) - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.NOT_SUPPORTED"` with the format name in `context` #### Scenario: decode returns a well-formed DecodeResult - **WHEN** a caller invokes `decode` on a registered codec with valid bytes - **THEN** the result has `channelData.length === channels`, a positive `sampleRate`, and a `format` matching the codec's `name` **Verified by:** `src/codecs/wav.test.ts::"decode returns a well-formed DecodeResult"` AND `src/codecs/wav.test.ts::"encode throws AUDIO_CODECS.NOT_SUPPORTED for a decode-only format"` --- ### Requirement: Registry decode and encode dispatch `CodecRegistry.decode(source, opts?)` SHALL resolve the source to bytes, detect or accept a hinted format, dispatch to the matching codec's `decode`, and return an `AudioDocument` built from the `DecodeResult` plus any `@suaveplan/audio-metadata` tags. `CodecRegistry.encode(doc, format, opts?)` SHALL render the document's PCM, dispatch to the codec registered for `format`, and return the encoded `Uint8Array`. A `decode` whose bytes match no codec and carry no usable hint SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.UNKNOWN_FORMAT`; an `encode` for a `format` with no registered codec SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.NOT_SUPPORTED`. #### Scenario: decode dispatches to the detected codec - **WHEN** `decode` is called with WAV bytes and no `format` hint - **THEN** detection selects the WAV codec and the returned `AudioDocument` carries the decoded `sampleRate`, `channels`, and `sourceLength` #### Scenario: unknown format throws AUDIO_CODECS.UNKNOWN_FORMAT - **WHEN** `decode` is called with bytes that match no registered codec and no `format` hint - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.UNKNOWN_FORMAT"` **Verified by:** `src/registry/registry.test.ts::"decode dispatches to the detected codec and builds an AudioDocument"` AND `src/registry/registry.test.ts::"decode throws AUDIO_CODECS.UNKNOWN_FORMAT for unrecognised bytes"` --- ### Requirement: Universal decodeSource entry point The package SHALL export `decodeSource(source, opts?)` accepting a `Source` (`string | URL | ArrayBuffer | Uint8Array | ReadableStream`) and returning a `Promise`. It SHALL resolve the source via `detectSource`, detect the format (honouring `opts.format` as a fallback hint), dispatch through the shared `CodecRegistry`, and invoke `opts.notify()` once the first page is ready and `opts.ondata(delta)` per decoded block. An unsupported or non-resolvable source SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.INVALID_SOURCE`. #### Scenario: decodeSource accepts every Source variant - **WHEN** `decodeSource` is called with a `Uint8Array`, an `ArrayBuffer`, and a `ReadableStream` of the same WAV payload - **THEN** all three return an `AudioDocument` whose PCM is byte-equal #### Scenario: invalid source throws AUDIO_CODECS.INVALID_SOURCE - **WHEN** `decodeSource` is called with a value that is not a recognised `Source` type - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.INVALID_SOURCE"` **Verified by:** `src/decode/decode-source.test.ts::"decodeSource accepts buffer, array-buffer, and stream sources"` AND `src/decode/decode-source.test.ts::"decodeSource throws AUDIO_CODECS.INVALID_SOURCE for an unrecognised source"` --- ### Requirement: Universal encodeTo entry point The package SHALL export `encodeTo(doc, destination, format, opts?)` accepting a `Destination` (`string` file path, `WritableStream`, or `(chunk: Uint8Array) => void` callback) and returning `Promise`. It SHALL encode the document via the shared `CodecRegistry`, then write the bytes to the destination — a `string` path via runtime-gated dynamic `import('node:fs/promises')`, a `WritableStream` via its writer, a callback by invocation. An encode failure SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.ENCODE_FAILED` carrying the underlying error as `cause`. #### Scenario: encodeTo writes to each Destination variant - **WHEN** `encodeTo` is called with the same document and format against a `WritableStream` and a callback - **THEN** the bytes delivered to both destinations are byte-equal to the registry's `encode` output #### Scenario: encode failure throws AUDIO_CODECS.ENCODE_FAILED - **WHEN** the selected codec's `encode` throws - **THEN** `encodeTo` throws `AudioCodecsError` whose `code === "AUDIO_CODECS.ENCODE_FAILED"` with the original error in `cause` **Verified by:** `src/encode/encode-to.test.ts::"encodeTo writes to a WritableStream and a callback destination"` AND `src/encode/encode-to.test.ts::"encodeTo throws AUDIO_CODECS.ENCODE_FAILED when the codec encode throws"` --- ### Requirement: Format detection via magic bytes The package SHALL export `detectSource(source)` returning `{ format, bytes?, path?, reader? }`. It SHALL read at most `HEADER_SIZE` (`256 * 1024`) bytes from the source head and pass them to `@suaveplan/audio-metadata`'s magic-byte detection. For a `string` server file path it SHALL open the file via dynamic `import('node:fs/promises')`, read the header, close the handle, and return `{ format, path }`. For an `http`/`https`/`data`/`blob` string or `URL` it SHALL `fetch()` and return `{ format, reader }`. For a buffer it SHALL detect over the header slice and return `{ format, bytes }`. Detection SHALL never throw for unknown content — it returns `format: undefined`. #### Scenario: detectSource sniffs format from the header slice - **WHEN** `detectSource` is called with a `Uint8Array` whose first bytes are a known WAV `RIFF` header - **THEN** the result `format` is `"wav"` and `bytes` is the original buffer #### Scenario: detectSource returns undefined format for unknown content - **WHEN** `detectSource` is called with bytes matching no known magic signature - **THEN** the result `format` is `undefined` and no error is thrown **Verified by:** `src/detect/detect-format.test.ts::"detectSource sniffs a known format from the header slice"` AND `src/detect/detect-format.test.ts::"detectSource returns undefined format for unrecognised content"` --- ### Requirement: Progressive streaming decode The package SHALL export `progressiveDecode(source, opts, registry)` that fills `AudioDocument.pages` as codec chunks arrive. It SHALL obtain a `StreamingDecoder` via the codec's `createDecoder()` when available, feed each source chunk through `decoder.write(chunk)`, drain `decoder.flush()` at end-of-stream, and accumulate decoded PCM into pages via a `pageAccumulator`. The returned `AudioDocument` SHALL be usable immediately — before decode completes — and edits chained onto it SHALL apply to the partial page set and extend automatically as further pages arrive. When the codec exposes no `createDecoder()`, `progressiveDecode` SHALL fall back to a single full-buffer `decode`. #### Scenario: document is usable before decode completes - **WHEN** a caller obtains the `AudioDocument` from `progressiveDecode` and runs a `trim` edit before the source stream is drained - **THEN** the edit is appended, `version` increments, and the compiled length tracks the page count as more pages arrive #### Scenario: streaming decode total equals non-streaming decode total - **WHEN** the same payload is decoded via `progressiveDecode` and via a single `CodecRegistry.decode` - **THEN** the two documents' `sourceLength` and concatenated PCM are equal **Verified by:** `src/stream/stream-decoder.test.ts::"returned document is editable before the source stream is drained"` AND `tests/property.test.ts::"streaming decode yields the same total samples as non-streaming decode"` --- ### Requirement: Streaming decode and encode transforms `CodecRegistry.decodeStream(source, format)` SHALL return a `ReadableStream` driven by the codec's `StreamingDecoder`, emitting one `AudioPage` per non-empty `DecodeResult`. `CodecRegistry.encodeStream(doc, format)` SHALL return a `ReadableStream` driven by the codec's `StreamingEncoder`. The package SHALL also export `createDecodeTransform(format)` returning a `@suaveplan/streams` `TransformStream` and `createEncodeTransform(format)` returning a `TransformStream`, so codec stages compose into `@suaveplan/streams` pipelines with native backpressure. A streaming operation on a codec lacking the corresponding factory SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.STREAM_ERROR`. #### Scenario: decodeStream emits one page per decoded chunk - **WHEN** a caller drains `decodeStream` over a chunked WAV source - **THEN** the concatenated `AudioPage` PCM equals the non-streaming `decode` output #### Scenario: streaming on a non-streaming codec throws AUDIO_CODECS.STREAM_ERROR - **WHEN** `decodeStream` is called for a codec that exposes no `createDecoder()` - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.STREAM_ERROR"` **Verified by:** `src/stream/stream-decoder.test.ts::"decodeStream emits one AudioPage per decoded chunk"` AND `src/stream/stream-encoder.test.ts::"createEncodeTransform composes into a @suaveplan/streams pipeline"` --- ### Requirement: Native WAV codec The default registry SHALL ship a native `WavCodec` requiring no external dependency, supporting decode, encode, and streaming. It SHALL parse the RIFF/`fmt`/`data` chunk structure, support 8/16/24/32-bit PCM and 32-bit float sample formats, and produce a `DecodeResult` whose `channelData` is normalised `Float32Array[]`. `encode` SHALL emit a spec-conformant RIFF WAV from `Float32Array[]` PCM. Malformed or truncated WAV bytes SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.DECODE_FAILED`. #### Scenario: WAV decode-then-encode round-trips PCM exactly - **WHEN** a caller decodes a 16-bit WAV then re-encodes the resulting `AudioDocument` as WAV - **THEN** the re-decoded PCM is byte-equal to the original decoded PCM #### Scenario: truncated WAV throws AUDIO_CODECS.DECODE_FAILED - **WHEN** `WavCodec.decode` is given a buffer whose `data` chunk length exceeds the available bytes - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.DECODE_FAILED"` **Verified by:** `src/codecs/wav.test.ts::"WAV decode-then-encode round-trips PCM exactly"` AND `src/codecs/wav.test.ts::"truncated WAV bytes throw AUDIO_CODECS.DECODE_FAILED"` --- ### Requirement: Native AIFF and CAF decoders The default registry SHALL ship native `AiffCodec` and `CafCodec` decoders requiring no external dependency. Both SHALL implement `canDecode` and `decode`, parse their respective container chunk structures, and produce a `DecodeResult` of normalised `Float32Array[]` PCM. Neither supports encoding or streaming: `encode` SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.NOT_SUPPORTED` and the optional streaming factories SHALL be omitted. #### Scenario: AIFF decodes to normalised float PCM - **WHEN** `AiffCodec.decode` is given a valid AIFF `FORM`/`COMM`/`SSND` buffer - **THEN** the result `channelData` is `Float32Array[]` with samples in `[-1, 1]` and `format === "aiff"` #### Scenario: CAF encode throws AUDIO_CODECS.NOT_SUPPORTED - **WHEN** a caller invokes `CafCodec.encode` - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.NOT_SUPPORTED"` **Verified by:** `src/codecs/aiff.test.ts::"AIFF decodes to normalised float PCM"` AND `src/codecs/caf.test.ts::"CAF encode throws AUDIO_CODECS.NOT_SUPPORTED"` --- ### Requirement: WASM-backed codecs loaded on demand The default registry SHALL ship `Mp3Codec`, `FlacCodec`, `OggVorbisCodec`, `OpusCodec`, `AacCodec`, `WebMCodec`, and `QoaCodec`, each wrapping a WASM module loaded via dynamic `import()` of an `optionalDependency` (e.g. `@audio/decode-mp3`, `@audio/decode-flac`). The WASM module SHALL be loaded lazily on first `decode`/`encode`/`createDecoder` call, not at package import time. When the optional dependency is not installed, the codec's first use SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.NOT_SUPPORTED` naming the missing module. MP3, FLAC, Opus, and QOA SHALL support encode; Ogg Vorbis, AAC, and WebM are decode-only. #### Scenario: WASM module loads lazily on first use - **WHEN** the package is imported but no WASM codec method is called - **THEN** no `@audio/*` dynamic `import()` has been evaluated #### Scenario: missing optional dependency throws AUDIO_CODECS.NOT_SUPPORTED - **WHEN** `Mp3Codec.decode` is called and `@audio/decode-mp3` is not installed - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.NOT_SUPPORTED"` naming the missing module **Verified by:** `src/codecs/mp3.test.ts::"WASM module is not loaded until first decode call"` AND `src/codecs/mp3.test.ts::"missing @audio/decode-mp3 throws AUDIO_CODECS.NOT_SUPPORTED"` --- ### Requirement: Memory budget enforcement `decodeSource` and `progressiveDecode` SHALL accept an optional `budget` (bytes) in `DecodeSourceOpts`. Before allocating pages they SHALL project required memory as `sampleRate × channels × durationSamples × 4` and, when the projection exceeds `budget`, abort decode and throw `AudioCodecsError` with code `AUDIO_CODECS.BUDGET_EXCEEDED` carrying the projected and budgeted byte counts in `context`. When `budget` is omitted no projection check SHALL run. #### Scenario: projected allocation over budget aborts decode - **WHEN** `decodeSource` is called with a `budget` smaller than the projected decoded-PCM size - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.BUDGET_EXCEEDED"` with `projectedBytes` and `budgetBytes` in `context`, before any page is allocated #### Scenario: decode within budget proceeds normally - **WHEN** `decodeSource` is called with a `budget` larger than the projected size - **THEN** decode completes and returns a fully-populated `AudioDocument` **Verified by:** `src/decode/decode-source.test.ts::"decodeSource throws AUDIO_CODECS.BUDGET_EXCEEDED when projection exceeds budget"` AND `src/decode/decode-source.test.ts::"decodeSource completes when projection is within budget"` --- ### Requirement: Source resolution across runtimes `detectSource` and `decodeSource` SHALL resolve a `string` or `URL` source by these rules: a string starting with `http`/`https`/`data`/`blob`, or any `URL`, resolves via `fetch()`; a string starting with `file:` resolves via dynamic `import('node:fs/promises')`; any other bare string resolves via `fs/promises.readFile` when running on a server runtime and via `fetch()` when running in a browser. `ArrayBuffer`, `Uint8Array`, and `ReadableStream` sources SHALL be consumed directly with no platform call. The `node:fs/promises` import SHALL only ever appear inside the runtime-gated branch. #### Scenario: http string resolves via fetch - **WHEN** `detectSource` is called with `"https://example.test/audio.wav"` - **THEN** the source is fetched and a `reader` is returned, with no `node:fs` import evaluated #### Scenario: file: string resolves via fs in a server runtime - **WHEN** `detectSource` is called with `"file:///tmp/audio.wav"` on a server runtime - **THEN** `node:fs/promises` is dynamically imported and the header is read from disk **Verified by:** `src/detect/detect-format.test.ts::"http and URL sources resolve via fetch"` AND `src/detect/detect-format.test.ts::"file: sources resolve via dynamically-imported node:fs"` --- ### Requirement: AbortSignal propagation `decodeSource`, `encodeTo`, `progressiveDecode`, and the streaming methods (`CodecRegistry.decodeStream`, `CodecRegistry.encodeStream`) SHALL accept an `AbortSignal` via their options object. An already-aborted signal SHALL cause the method to throw `AudioCodecsError` with code `AUDIO_CODECS.STREAM_ERROR` before any source read or page allocation. A signal aborted mid-decode SHALL terminate at the next chunk boundary with the same error, release any open file handle or stream reader, and discard partial pages. #### Scenario: pre-aborted signal short-circuits decode - **WHEN** `decodeSource` is called with an already-aborted signal - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.STREAM_ERROR"` before any source byte is read #### Scenario: mid-decode abort releases the reader - **WHEN** a signal is aborted after the first chunk of a `progressiveDecode` stream is consumed - **THEN** decode terminates at the next chunk boundary, the stream reader is released, and `AUDIO_CODECS.STREAM_ERROR` is thrown **Verified by:** `src/decode/decode-source.test.ts::"decodeSource throws AUDIO_CODECS.STREAM_ERROR for a pre-aborted signal"` AND `src/stream/stream-decoder.test.ts::"progressiveDecode honours mid-stream abort and releases the reader"` --- ### Requirement: Zod boundary validation Every public options object — `DecodeOpts`, `EncodeOpts`, `DecodeSourceOpts`, `AudioCodec`, `StreamingDecoder`, `StreamingEncoder` — SHALL have a Zod schema exported from a sibling `*.schemas.ts`, and every public entry point (`decodeSource`, `encodeTo`, `CodecRegistry.register`, `CodecRegistry.decode`, `CodecRegistry.encode`) SHALL validate its options or codec argument against the schema before use. A validation failure SHALL throw `AudioCodecsError` with code `AUDIO_CODECS.INVALID_SOURCE` carrying the Zod issue list in `context`. #### Scenario: invalid options are rejected at the boundary - **WHEN** `decodeSource` is called with a `budget` that is a negative number - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.INVALID_SOURCE"` with the Zod issues in `context` #### Scenario: malformed codec is rejected at registration - **WHEN** `CodecRegistry.register` is called with an object missing a `name` - **THEN** it throws `AudioCodecsError` whose `code === "AUDIO_CODECS.INVALID_SOURCE"` with the Zod issues in `context` **Verified by:** `src/decode/decode-source.test.ts::"decodeSource rejects invalid options with AUDIO_CODECS.INVALID_SOURCE"` AND `src/registry/registry.test.ts::"register rejects a malformed codec via Zod validation"` --- ### Requirement: Structured error codes The package SHALL export `AudioCodecsErrorCode`, `AUDIO_CODECS_ERROR_METADATA`, and `registerAudioCodecsErrorCodes()`, registering the codes `AUDIO_CODECS.UNKNOWN_FORMAT`, `AUDIO_CODECS.DECODE_FAILED`, `AUDIO_CODECS.ENCODE_FAILED`, `AUDIO_CODECS.STREAM_ERROR`, `AUDIO_CODECS.NOT_SUPPORTED`, `AUDIO_CODECS.INVALID_SOURCE`, and `AUDIO_CODECS.BUDGET_EXCEEDED` with `@suaveplan/error`. Every error thrown by the package SHALL be an `AudioCodecsError` extending `SuaveplanError` and carry one of these codes, `cause`, and `context`. Every registered code SHALL be reachable from a real call site exercised by tests. #### Scenario: every registered code is thrown by a call site - **WHEN** the test suite runs - **THEN** each code in `AUDIO_CODECS_ERROR_METADATA` is asserted thrown by at least one test **Verified by:** `src/error-codes.test.ts::"every AUDIO_CODEC code is reachable from a call site"` --- ### Requirement: OpenTelemetry instrumentation The package SHALL instrument its public entry points via `@opentelemetry/api`: spans `codec.decode`, `codec.encode`, and `codec.detect`; counters `audio_codec_decode_total` and `audio_codec_encode_total`; histograms `audio_codec_decode_duration_ms` and `audio_codec_encode_duration_ms`. Spans SHALL carry the resolved `format` as an attribute. Instrumentation SHALL be a no-op when no exporter is registered and SHALL NOT run as an import-time side effect. #### Scenario: instrumentation is inert without an exporter - **WHEN** the package is imported and used with no OTel exporter registered - **THEN** no error is thrown and no global state is mutated at import time #### Scenario: a decode records a span and histogram - **WHEN** `decodeSource` runs against a real in-memory `MeterProvider` and `TracerProvider` - **THEN** `codec.decode` records one span carrying the `format` attribute, `audio_codec_decode_total` increments by 1, and `audio_codec_decode_duration_ms` records one observation **Verified by:** `src/registry/registry.test.ts::"decode records a span, counter, and histogram when an exporter is registered"` AND `tests/purity.test.ts::"importing the package mutates no global state"` --- ### Requirement: Subpath exports per feature module `package.json` SHALL declare one `exports` entry per feature module (`./registry/registry`, `./decode/decode-source`, `./encode/encode-to`, `./detect/detect-format`, `./stream/stream-decoder`, `./stream/stream-encoder`, `./codecs/wav`, `./codecs/mp3`, `./codecs/flac`) in addition to the root `.` barrel, each resolving to a typed `import` condition. `sideEffects` SHALL be `false`. #### Scenario: each feature module is independently importable - **WHEN** a consumer imports `@suaveplan/audio-codecs/codecs/wav` - **THEN** the import resolves to the WAV codec module without pulling the whole package barrel or any WASM codec **Verified by:** `tests/exports.test.ts::"every feature module has a subpath export entry"`