feat(audio): open the AI noise suppression gate after passed acceptance

Two-person call on 2026-08-17: filter effective, keyboard gone, unmuting intact
on both sides - the acceptance that gates this flag, per the standing rule from
the v0.5.0 incident. The gate flips to true, which brings the checkbox and
slider back into the in-call settings.

The dev override stays in the code as the tool for the next test phase of this
kind; a test pins that it does nothing without the regular setting. The rollback
lever for any regression is the gate itself, not a deployment revert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thore Cimbal
2026-08-17 12:00:00 +00:00
co-authored by Claude Opus 4.8
parent df4e5eeb85
commit e3f8a8570a
5 changed files with 47 additions and 45 deletions
+5 -5
View File
@@ -66,9 +66,11 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => {
expect(calls).toEqual([]);
});
test("a stale enabled setting without the dev override touches nothing", async () => {
// sorbs Zustand nach dem Diagnosefenster: Einstellung an, Tor zu.
aiNoiseSuppressionSetting.setValue(true);
test("the dev override alone (setting off) touches nothing", async () => {
// UND-Verknuepfung im Praedikat: der Schalter der Testphase darf ohne die
// eigentliche Einstellung nichts bewirken.
aiNoiseSuppressionDevSetting.setValue(true);
aiNoiseSuppressionSetting.setValue(false);
const { track, calls } = makeTrack();
await applyAiNoiseSuppression(track, logger);
@@ -79,7 +81,6 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => {
test("sets the audio context BEFORE attaching the processor", async () => {
// Genau die Reihenfolge, an der v0.5.0 gescheitert ist: setProcessor
// ohne AudioContext auf dem Track wirft in LiveKit.
aiNoiseSuppressionDevSetting.setValue(true);
aiNoiseSuppressionSetting.setValue(true);
const { track, calls } = makeTrack();
@@ -91,7 +92,6 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => {
test("a failing processor attach is contained and does not throw", async () => {
// Der Track ist zu diesem Zeitpunkt publiziert - ein Filterfehler darf
// den Call nicht mehr erreichen.
aiNoiseSuppressionDevSetting.setValue(true);
aiNoiseSuppressionSetting.setValue(true);
const { track, setProcessor } = makeTrack();
setProcessor.mockRejectedValue(new Error("wasm sagt nein"));
+12 -11
View File
@@ -39,19 +39,20 @@ import {
const ASSET_PFAD = "assets/dfn3";
/**
* Feature-Tor: solange `false`, ist der Filter fuer ALLE Clients aus - auch
* fuer solche, die die Einstellung frueher aktiviert haben (localStorage).
* Feature-Tor: bei `false` ist der Filter fuer ALLE Clients aus - auch fuer
* solche, die die Einstellung frueher aktiviert haben (localStorage).
*
* Warum: `LocalAudioTrack.setProcessor()` verlangt einen AudioContext auf dem
* Track. Den bekommt er nur, wenn der LiveKit-Raum mit `webAudioMix` gebaut
* wird - und genau das steht in `ConnectionFactory.ts` als unerprobtes
* Upstream-TODO auskommentiert. Ohne AudioContext wirft LiveKit beim Entmuten
* "Audio context needs to be set on LocalAudioTrack in order to enable
* processors", und es wird nie ein Audio-Track publiziert (Produktionsvorfall
* 2026-08-16, v0.5.0). Erst freischalten, wenn `webAudioMix` entschieden und
* im Call zu zweit getestet ist (Folgearbeit zu ADR-0018).
* Geschichte: v0.5.0 setzte den Prozessor ueber die Capture-Defaults, ohne
* dass der Track einen AudioContext hatte - LiveKit warf "Audio context needs
* to be set on LocalAudioTrack in order to enable processors", und das
* Entmuten brach fuer alle (Produktionsvorfall 2026-08-16). Seit Weg B
* (#0054) haengt sich der Filter NACH der Publikation an den Mikrofon-Track,
* mit eigenem AudioContext nur dort. Abnahme im Call zu zweit bestanden am
* 2026-08-17 (Filter wirksam, Tastatur weg, Entmuten intakt) - seitdem ist
* das Tor offen. Bei einer Regression zuerst wieder auf `false` stellen:
* das legt den Filter still, ohne die Auslieferung anzufassen.
*/
export const AI_NOISE_SUPPRESSION_AVAILABLE = false;
export const AI_NOISE_SUPPRESSION_AVAILABLE = true;
/**
* Ist der Filter wirksam? Nur wenn das Tor offen ist (oder der
@@ -149,27 +149,11 @@ describe("ECConnectionFactory - AI noise suppression off-path (ADR-0018)", () =>
});
});
test("a stale enabled setting (localStorage) is neutralized by the feature gate", () => {
// Genau der Zustand eines Clients, der den Filter aktiviert hatte, bevor
// das Tor geschlossen wurde: Einstellung an, Tor zu.
aiNoiseSuppressionSetting.setValue(true);
noiseSuppressionSetting.setValue(true);
const options = buildRoomOptions();
expect(options.audioCaptureDefaults).not.toHaveProperty("processor");
expect(options.audioCaptureDefaults).toMatchObject({
noiseSuppression: true,
});
aiNoiseSuppressionSetting.setValue(false);
});
test("even with the filter active (dev override) the defaults carry no processor key", () => {
test("with the filter active the defaults carry no processor key either (way B)", () => {
// Weg B aus #0054: der Prozessor wird NACH der Publikation an den Track
// gehaengt, nie ueber die Capture-Defaults - die getUserMedia-Constraints
// muessen in JEDEM Zustand upstream-identisch bleiben. Hier aendert sich
// nur noiseSuppression, damit nicht zwei Filter gegeneinander arbeiten.
aiNoiseSuppressionDevSetting.setValue(true);
aiNoiseSuppressionSetting.setValue(true);
noiseSuppressionSetting.setValue(true);
@@ -179,9 +163,24 @@ describe("ECConnectionFactory - AI noise suppression off-path (ADR-0018)", () =>
noiseSuppression: false,
});
aiNoiseSuppressionDevSetting.setValue(false);
aiNoiseSuppressionSetting.setValue(false);
});
test("the dev override alone (setting off) changes nothing", () => {
// Das Praedikat ist eine UND-Verknuepfung: der Entwickler-Schalter der
// Testphase darf ohne die eigentliche Einstellung nichts bewirken.
aiNoiseSuppressionDevSetting.setValue(true);
aiNoiseSuppressionSetting.setValue(false);
noiseSuppressionSetting.setValue(true);
const options = buildRoomOptions();
expect(options.audioCaptureDefaults).not.toHaveProperty("processor");
expect(options.audioCaptureDefaults).toMatchObject({
noiseSuppression: true,
});
aiNoiseSuppressionDevSetting.setValue(false);
});
});
describe("ECConnectionFactory - ControlledAudioDevice", () => {