111 lines
6.5 KiB
Markdown
111 lines
6.5 KiB
Markdown
<!-- markdownlint-disable MD024 -->
|
|
|
|
# Capability Spec Delta: audio-ir-room-plugin-pool (add-audio-ir-room-plugin-pool)
|
|
|
|
**Package**: `@suaveplan/audio-ir-room-plugin-pool`
|
|
**Status**: PROPOSED
|
|
|
|
> Every requirement carries a `**Verified by:**` line.
|
|
|
|
## ADDED Requirements
|
|
|
|
### Requirement: Manifest declares one pool IR with a fallback:// URI
|
|
|
|
The package SHALL export `manifest: IrManifest` with `packageSlug: "pool"`, `family: "room"`, AND `entries: readonly IrManifestEntry[]` containing exactly one entry:
|
|
|
|
```ts
|
|
{
|
|
id: "pool-outdoor-swimming-pool",
|
|
family: "room",
|
|
uri: buildFallbackUri([
|
|
"bundle://audio-ir-room-plugin-pool/outdoor-swimming-pool.wav",
|
|
"https://swch.b-cdn.net/_/audio/ir/room/roompool.wav",
|
|
]),
|
|
channels: <decoded>,
|
|
sampleRate: <decoded>,
|
|
gainDb: 0,
|
|
license: <see README>,
|
|
source: "Mountain Duck media library + https://swch.b-cdn.net CDN",
|
|
}
|
|
```
|
|
|
|
`channels` AND `sampleRate` SHALL be the values measured by decoding the bundled file at manifest-authoring time. The entry's `uri` SHALL parse through `parseFallbackUri` (from `@suaveplan/storage-uri-plugin-fallback`) AND yield two candidates whose first element starts with `bundle://` AND whose second element starts with `https://`.
|
|
|
|
#### Scenario: manifest parses through IrManifestSchema
|
|
|
|
- **WHEN** `IrManifestSchema.parse(manifest)` is called
|
|
- **THEN** the result deep-equals the export AND the lone entry's `id === "pool-outdoor-swimming-pool"` AND its `family === "room"`
|
|
|
|
#### Scenario: URI parses through parseFallbackUri with two candidates in order
|
|
|
|
- **WHEN** `parseFallbackUri(manifest.entries[0].uri)` is called
|
|
- **THEN** the result's `candidates` has length 2, `candidates[0]` starts with `"bundle://audio-ir-room-plugin-pool/"`, `candidates[1] === "https://swch.b-cdn.net/_/audio/ir/room/roompool.wav"`
|
|
|
|
**Verified by:** `src/manifest/manifest.test.ts::"manifest parses through IrManifestSchema"` AND `src/manifest/manifest.test.ts::"URI parses through parseFallbackUri with two ordered candidates"`
|
|
|
|
---
|
|
|
|
### Requirement: `register(registry, resolver)` adds the bundle plugin and registers the entry
|
|
|
|
The package SHALL export `register(registry: IrRegistry, resolver: StorageUriResolver): void` that:
|
|
|
|
1. Calls `resolver.use(bundleUriPlugin({ packageSlug: "audio-ir-room-plugin-pool", rootUrl: new URL("../files/", import.meta.url) }))` exactly once.
|
|
2. Calls `registerManifest(registry, manifest)`.
|
|
|
|
The function SHALL NOT register the `fallback` plugin — that is the consumer's responsibility (one `fallbackUriPlugin` per resolver, shared across every fallback-using package). The function SHALL NOT register an HTTPS plugin either.
|
|
|
|
#### Scenario: register adds the bundle plugin and registers the entry
|
|
|
|
- **WHEN** `register(registry, resolver)` is called
|
|
- **THEN** `resolver.dispatch("bundle://audio-ir-room-plugin-pool/outdoor-swimming-pool.wav")` succeeds AND `registry.has("pool-outdoor-swimming-pool") === true`
|
|
|
|
#### Scenario: register does NOT register the fallback plugin
|
|
|
|
- **WHEN** `register(registry, resolver)` is called against a resolver that has no `fallback` plugin
|
|
- **THEN** attempting to dispatch `"fallback://?u=..."` through the same resolver throws `STORAGE_URI.UNKNOWN_SCHEME` (proving the package did not silently add it)
|
|
|
|
**Verified by:** `src/register/register.test.ts::"adds bundle plugin and registers the entry"` AND `src/register/register.test.ts::"does not register the fallback plugin"`
|
|
|
|
---
|
|
|
|
### Requirement: Bundle-first end-to-end resolution
|
|
|
|
The package SHALL include an integration test asserting that when the bundle file is present on disk AND the consumer has wired `bundleUriPlugin`, `fallbackUriPlugin`, AND an HTTPS plugin into the resolver, the loader's `load("pool-outdoor-swimming-pool")` resolves through the bundle candidate WITHOUT touching the HTTPS candidate.
|
|
|
|
#### Scenario: bundle candidate wins; HTTPS candidate not fetched
|
|
|
|
- **WHEN** the bundle file exists at `<package-root>/files/outdoor-swimming-pool.wav` AND a spy is attached to the HTTPS plugin's `adapter.get` AND `loader.load("pool-outdoor-swimming-pool")` is called
|
|
- **THEN** the call resolves to a frozen `ImpulseResponse` AND the HTTPS spy's call count is exactly 0
|
|
|
|
**Verified by:** `tests/integration/bundle-only.test.ts::"bundle candidate wins and HTTPS candidate is not fetched"`
|
|
|
|
---
|
|
|
|
### Requirement: Fallback walk to HTTPS on bundle miss
|
|
|
|
The package SHALL include an integration test asserting that when the bundle file is ABSENT at fixture-setup time, the loader's `load("pool-outdoor-swimming-pool")` falls through to the HTTPS candidate (served by a `node:http` fixture serving valid WAV bytes), AND that the `fallback_uri.fallback_total{candidate_index:0, outcome:"transient"}` counter (from the shipped `fallback://` plugin) is incremented exactly once. The test SHALL stand up the HTTP fixture in `beforeAll` AND tear it down in `afterAll`.
|
|
|
|
#### Scenario: bundle missing → HTTPS succeeds via fallback walk
|
|
|
|
- **WHEN** the `<package-root>/files/outdoor-swimming-pool.wav` symlink is replaced with a temp dir containing NO `outdoor-swimming-pool.wav` (so the bundle adapter returns `BUNDLE_URI.NOT_FOUND`) AND the HTTPS fixture at `http://localhost:<port>/roompool.wav` serves valid WAV bytes that decode to a known channel/sample-rate pair AND `loader.load("pool-outdoor-swimming-pool")` is called
|
|
- **THEN** the call resolves to a frozen `ImpulseResponse` matching the HTTPS payload AND `fallback_uri.fallback_total{candidate_index:0, outcome:"transient"}` is incremented exactly once AND `audio_ir_loader.bytes_read_total{family:"room"}` is incremented exactly once
|
|
|
|
> NOTE: The integration test uses a substituted `rootUrl` pointing at an empty temp directory rather than literally deleting the on-disk WAV file from the package. This keeps the on-disk artifact stable across test runs while still exercising the bundle-miss branch of the fallback walk.
|
|
|
|
**Verified by:** `tests/integration/fallback-walk.test.ts::"bundle miss falls through to HTTPS and increments fallback counter"`
|
|
|
|
---
|
|
|
|
### Requirement: Bundled WAV exists on disk at build time
|
|
|
|
The package SHALL ship `files/outdoor-swimming-pool.wav` in its tarball. The file SHALL be non-empty AND SHALL decode successfully through `decodeWav` from `@suaveplan/audio-decode-wav`.
|
|
|
|
#### Scenario: bundle file exists and is non-empty
|
|
|
|
- **WHEN** the test reads `<package-root>/files/outdoor-swimming-pool.wav` via `node:fs`
|
|
- **THEN** the read succeeds AND the file's byte length is `> 0` AND `decodeWav(bytes)` resolves successfully
|
|
|
|
**Verified by:** `tests/integration/file-presence.test.ts::"outdoor-swimming-pool.wav exists on disk and decodes"`
|
|
|
|
<!-- markdownlint-enable MD024 -->
|