Initial Commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# audio-core spec delta — refresh-audio-queue-2026-05-23
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: AnalysisOp operator contract
|
||||
|
||||
The audio-core package SHALL expose an `AnalysisOp<TConfig, TResult>` operator contract that every `@suaveplan/audio-analysis-*` package implements, and a `createAnalysisOp` builder that constructs conforming operators with Zod-validated config, OTel instrumentation, and AbortSignal-aware cancellation.
|
||||
|
||||
`AnalysisOp` is a discriminated operator with: `name: string` (unique within an analysis pipeline), `version: string` (semver), `capabilities: AnalysisCapabilities` (streaming/offline/realtime/blocking flags), `configSchema: z.ZodTypeAny`, `run(doc: AudioDocument, config: TConfig, opts?: { signal?: AbortSignal }): Promise<TResult>`, and `runStream?(source: AsyncIterable<AnalysisFrame>, config: TConfig, opts?: { signal?: AbortSignal }): AsyncIterable<TResult>`.
|
||||
|
||||
`AnalysisResult` is the base shape every analyser extends — `{ analyserName: string; analyserVersion: string; sampleRate: number; durationSamples: number; computedAt: number }`. Concrete analyser results add their own fields on top.
|
||||
|
||||
#### Scenario: builder validates config via Zod
|
||||
|
||||
- **WHEN** `createAnalysisOp({ name, version, capabilities, configSchema, run })` is called and a downstream caller invokes `op.run(doc, badConfig)`
|
||||
- **THEN** the builder MUST reject with `AudioError("AUDIO_CORE.ANALYSIS_INVALID_CONFIG")` carrying the Zod issues on `context.issues`, before `run` is invoked
|
||||
|
||||
#### Scenario: builder instruments runs via OpenTelemetry
|
||||
|
||||
- **WHEN** `op.run(doc, validConfig)` is called and succeeds
|
||||
- **THEN** the builder MUST emit one OTel span named `analysis.${name}` with attributes `{ "analysis.name": name, "analysis.version": version, "audio.sample_rate": doc.sampleRate, "audio.duration_samples": doc.sourceLength }`, increment counter `audio_analysis_runs_total{analysis=${name}}`, and record histogram `audio_analysis_duration_ms{analysis=${name}}`
|
||||
|
||||
#### Scenario: builder forwards AbortSignal
|
||||
|
||||
- **WHEN** `op.run(doc, validConfig, { signal })` is invoked and `signal.aborted === true` before `run` returns
|
||||
- **THEN** the inner `run` callback MUST observe the signal and the builder MUST surface the abort as `AudioError("AUDIO_CORE.ANALYSIS_ABORTED")`
|
||||
|
||||
#### Scenario: capability flags advertise streaming support
|
||||
|
||||
- **WHEN** a caller inspects `op.capabilities.supportsStream`
|
||||
- **THEN** the value MUST equal `typeof op.runStream === "function"`
|
||||
|
||||
### Requirement: AnalysisFrame streaming window descriptor
|
||||
|
||||
The audio-core package SHALL export an `AnalysisFrame` shape — `{ samples: Float32Array[]; sampleRate: number; channelCount: number; startSample: number; sampleCount: number }` — usable as the streaming-input element type for any `AnalysisOp.runStream` implementation. The shape is types-only; no runtime construction helper is mandated.
|
||||
|
||||
#### Scenario: streaming analyser consumes AnalysisFrame iterable
|
||||
|
||||
- **WHEN** a spectrum analyser's `runStream(source, config)` is invoked with an `AsyncIterable<AnalysisFrame>`
|
||||
- **THEN** the analyser MUST process each frame independently and MAY yield zero, one, or many `AnalysisResult`-extending values per input frame
|
||||
Reference in New Issue
Block a user