Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# Add `@suaveplan/audio-ir-room-plugin-pool`
**Package**: `@suaveplan/audio-ir-room-plugin-pool`
**Status**: PROPOSED
**Type**: new-package
## Why
Genesis-original IR content package. Ships the outdoor-swimming-pool room IR (from `/mnt/c/Users/Administrator/Mountain Duck/74a17879-9780-426d-8bd9-7ed805f53d41/media/_/audio/ir/room/outdoor-swimming-pool.wav`) as a bundle-local asset PAIRED with `https://swch.b-cdn.net/_/audio/ir/room/roompool.wav` as the CDN fallback. Serves as the canonical example of the **bundle + fallback** content-package pattern — every future `audio-ir-{family}-plugin-<name>` package that wants a CDN fallback follows this shape.
The URI is a `fallback://?u=<bundle>&u=<https>` URI built via `buildFallbackUri` from `@suaveplan/storage-uri-plugin-fallback`. The shipped `fallback://` plugin handles ordered walk + transient classification + per-candidate `ALL_CANDIDATES_FAILED` cause chain; this package only declares the manifest.
## What changes
- ADDED: `manifest: IrManifest` — `{ packageSlug: "pool", family: "room", entries: [<pool-outdoor-swimming-pool>] }`.
- ADDED: `register(registry: IrRegistry, resolver: StorageUriResolver): void` — registers the package-scoped `bundleUriPlugin` on the resolver AND calls `registerManifest`.
- ADDED: `files/outdoor-swimming-pool.wav` — bundled binary asset.
- ADDED: `entries[0]` with id `pool-outdoor-swimming-pool` AND `uri = buildFallbackUri(["bundle://audio-ir-room-plugin-pool/outdoor-swimming-pool.wav", "https://swch.b-cdn.net/_/audio/ir/room/roompool.wav"])`.
## Out of scope
- Decoding — `audio-ir-loader`.
- Resolver setup — consumers wire `bundleUriPlugin`, `fallbackUriPlugin`, and an HTTP plugin (e.g. `@suaveplan/storage-http` or any sibling exposing the `https` scheme) at composition time.
- CDN upload — this change covers only the local manifest declaration; CDN is assumed pre-populated at `https://swch.b-cdn.net/_/audio/ir/room/roompool.wav`.
## Source mapping
- Bundle file: `/mnt/c/Users/Administrator/Mountain Duck/74a17879-9780-426d-8bd9-7ed805f53d41/media/_/audio/ir/room/outdoor-swimming-pool.wav` (Mountain Duck media library).
- CDN file: `https://swch.b-cdn.net/_/audio/ir/room/roompool.wav` (existing CDN object — verify reachability at manifest-authoring time).
Cross-cutting model: `openspec/changes/add-audio-ir-core/design.md`.
### Requirements with no source line
| Requirement | Justification |
|---|---|
| every spec requirement | Genesis-original content package |
## Verification approach
- `src/manifest/manifest.test.ts` — manifest parses through `IrManifestSchema`; the entry's `uri` parses through `parseFallbackUri` AND yields two candidates in declared order.
- `src/register/register.test.ts` — `register(registry, resolver)` adds the bundle plugin AND registers the entry; does NOT add the fallback plugin (that's resolver-owned at composition time).
- `tests/integration/bundle-only.test.ts` — wire `bundleUriPlugin` and `fallbackUriPlugin` and an HTTP plugin; verify the bundle candidate succeeds first and the HTTPS candidate is NOT fetched.
- `tests/integration/fallback-walk.test.ts` — DELETE the bundle file at fixture-setup time; assert the loader falls through to the HTTPS fixture (served by a `node:http` server) and decodes successfully; assert `fallback_uri.fallback_total{candidate_index:0, outcome:"transient"}` is incremented exactly once.
## Impact
### Affected dependents
No dependents. Verified by `bunx turbo ls --filter=...@suaveplan/audio-ir-room-plugin-pool`.

View File

@@ -0,0 +1,110 @@
<!-- 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 -->

View File

@@ -0,0 +1,71 @@
# Tasks — Add `@suaveplan/audio-ir-room-plugin-pool`
> Prereqs: `add-audio-ir-core`, `add-audio-ir-loader`, `add-audio-decode-wav` must reach SHIPPED before §5 integration tests can pass.
## 1. Spec authoring
- [ ] 1.1 Read `openspec/AGENTS.md` §§ 13, 14, 14a, 2c, 6a, 193
- [ ] 1.2 Read `openspec/changes/add-audio-ir-core/design.md`
- [ ] 1.3 `proposal.md` (incl. `## Impact`)
- [ ] 1.4 `specs/audio-ir-room-plugin-pool/spec.md` — every requirement carries `**Verified by:**`
- [ ] 1.5 Working-notes checklist
## 2. Package scaffold
- [ ] 2.1 `mkdir -p packages/audio-ir/audio-ir-room-plugin-pool/{src/{manifest,register},files,tests/integration}`
- [ ] 2.2 `package.json` — `version: 0.0.0`, `sideEffects: false`, `files: ["dist", "files", "README.md"]`, deps `@suaveplan/{audio-ir-core,storage-uri,storage-uri-plugin-bundle,storage-uri-plugin-fallback,error,types}`, dev `@suaveplan/{audio-ir-loader,audio-decode-wav,storage-http}`, `zod`, `tslib`
- [ ] 2.3 `tsconfig.lib.json`, `vite.config.ts`
- [ ] 2.4 Subpath exports: `.`, `./manifest`, `./register`
- [ ] 2.5 `README.md` — `## License & sources` recording Mountain Duck path + the CDN URL
## 3. Asset import
- [ ] 3.1 Copy `/mnt/c/Users/Administrator/Mountain Duck/74a17879-9780-426d-8bd9-7ed805f53d41/media/_/audio/ir/room/outdoor-swimming-pool.wav` → `packages/audio-ir/audio-ir-room-plugin-pool/files/outdoor-swimming-pool.wav`
- [ ] 3.2 Verify the file exists, is non-empty, and decodes through `decodeWav`
- [ ] 3.3 Record decoded `channels` and `sampleRate` — the manifest constants
- [ ] 3.4 Verify `https://swch.b-cdn.net/_/audio/ir/room/roompool.wav` is reachable AND returns a `200` with `audio/wav` content type AND decodes through `decodeWav` (smoke test at authoring time only; the integration test uses a `node:http` fixture, not the live CDN)
## 4. Implementation
- [ ] 4.1 `src/manifest/manifest.ts` + `.md` — `manifest: IrManifest` with the single entry; URI built via `buildFallbackUri([...])`
- [ ] 4.2 `src/register/register.ts` + `.md` — `register(registry, resolver)`
- [ ] 4.3 `src/index.ts` — explicit named re-exports
## 5. Tests
- [ ] 5.1 `src/manifest/manifest.test.ts` — every scenario from manifest requirement
- [ ] 5.2 `src/register/register.test.ts` — every scenario from register requirement
- [ ] 5.3 `tests/integration/file-presence.test.ts` — bundle file exists + decodes
- [ ] 5.4 `tests/integration/bundle-only.test.ts` — bundle wins; HTTPS not fetched
- [ ] 5.5 `tests/integration/fallback-walk.test.ts` — bundle-miss `rootUrl` substitution + `node:http` fixture for HTTPS candidate
- [ ] 5.6 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for async)
## 6. Documentation
- [ ] 6.1 Sibling `.md` for every `.ts` (≥200 words, basic + advanced examples)
- [ ] 6.2 README `## Modules` + `## License & sources`
- [ ] 6.3 Update root `CLAUDE.md` Audio IR subsection
## 7. Pre-archive verification (5-gate triple)
- [ ] 7.1 `bun run check` — 0/0/0/0
- [ ] 7.2 `bunx turbo lint typecheck test --filter=@suaveplan/audio-ir-room-plugin-pool` — green
- [ ] 7.3 `bunx turbo test:coverage --filter=@suaveplan/audio-ir-room-plugin-pool` — 100/100/100/100
- [ ] 7.4 `bun scripts/openspec-conformance.ts @suaveplan/audio-ir-room-plugin-pool` — exit 0
- [ ] 7.5 `bunx turbo build --filter=@suaveplan/audio-ir-room-plugin-pool` — green
## 8. Pre-archive 7-row checklist (AGENTS §13)
- [ ] 8.1 Manifest literal types preserved through export
- [ ] 8.2 Zod schemas wired (manifest + fallback URI parse)
- [ ] 8.3 No package-owned error codes — reuses `AUDIO_IR.*`, `BUNDLE_URI.*`, `FALLBACK_URI.*`
- [ ] 8.4 Strict-mode behaviors tested
- [ ] 8.5 OTel from upstream packages — counters asserted in `fallback-walk.test.ts`
- [ ] 8.6 Lifecycle dedup tested (double-register, double-load via cache)
- [ ] 8.7 No retroactive tick
## 9. Archive
- [ ] 9.1 Fold delta into new `openspec/specs/audio-ir-room-plugin-pool/spec.md`
- [ ] 9.2 Move folder to `openspec/archive/<yyyy-mm-dd>-add-audio-ir-room-plugin-pool/`
- [ ] 9.3 Refresh `MANIFEST.md`