Files
2026-05-27 23:29:53 +01:00

6.5 KiB

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:

{
  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"