feat(voice): add noise suppression backends and rollout (#2706)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-11 22:24:05 +02:00
1 parent b30a4f5d14
commit 172791316b
97 files changed
+10877 -3157

No files matched your search

@@ -1,5 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
type VoiceNoiseSuppressionConfig,
} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas';
import {
DEFAULT_EXPERIMENT_DELIVERY_CONFIG,
type ExperimentDeliveryConfig,
} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
import {afterEach, describe, expect, it, vi} from 'vitest';
import {setCassandraQueryExecutorForTesting} from '../database/CassandraQueryExecution';
import type {PreparedQuery} from '../database/CassandraTypes';
@@ -11,6 +19,9 @@ import {
type InstanceRegistrationConfig,
} from './InstanceConfigRepository';
const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
class CountingInMemoryCassandraQueryExecutor extends InMemoryCassandraQueryExecutor {
instanceConfigSelects = 0;
@@ -147,6 +158,148 @@ describe('InstanceConfigRepository', () => {
});
});
it('returns the default voice noise suppression config when the key is absent', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await expect(repository.getVoiceNoiseSuppressionConfig()).resolves.toEqual(DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG);
});
it.each([
{name: 'unparseable text', stored: 'not-json'},
{name: 'a json array', stored: '[]'},
{name: 'out-of-range values', stored: '{"rollout_basis_points":99999}'},
{name: 'an unknown backend', stored: '{"default_backend":"magic"}'},
])('falls back to the default voice noise suppression config for $name', async ({stored}) => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(VOICE_NOISE_SUPPRESSION_CONFIG_KEY, stored);
await expect(repository.getVoiceNoiseSuppressionConfig()).resolves.toEqual(DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG);
});
it('round-trips a stored voice noise suppression config', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
const config: VoiceNoiseSuppressionConfig = {
...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
enabled: true,
config_version: 3,
default_backend: 'rnnoise',
enabled_backends: ['none', 'standard', 'rnnoise'],
allow_user_override: false,
rollout_basis_points: 2500,
rollout_salt: 'voice-ns-v2',
included_user_ids: ['1400000000000000001'],
excluded_user_ids: ['1400000000000000002'],
guild_overrides: [{guild_id: '2400000000000000001', backend: 'rnnoise'}],
stereo_enabled: true,
suppression_strength: 55,
};
await repository.setVoiceNoiseSuppressionConfig(config);
await expect(repository.getVoiceNoiseSuppressionConfig()).resolves.toEqual(config);
});
it('fills newly added voice noise suppression fields from the schema defaults', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(
VOICE_NOISE_SUPPRESSION_CONFIG_KEY,
JSON.stringify({enabled: true, config_version: 2, rollout_basis_points: 1000}),
);
await expect(repository.getVoiceNoiseSuppressionConfig()).resolves.toEqual({
...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
enabled: true,
config_version: 2,
rollout_basis_points: 1000,
});
});
it('returns the default experiment delivery config when the key is absent', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await expect(repository.getExperimentDeliveryConfig()).resolves.toEqual(DEFAULT_EXPERIMENT_DELIVERY_CONFIG);
});
it.each([
{name: 'unparseable text', stored: 'not-json'},
{name: 'a json array', stored: '[]'},
{name: 'an out-of-range poll interval', stored: '{"poll_interval_seconds":1}'},
{name: 'an out-of-range jitter', stored: '{"poll_jitter_percent":99}'},
{name: 'a non-numeric poll interval', stored: '{"poll_interval_seconds":"often"}'},
])('falls back to the default experiment delivery config for $name', async ({stored}) => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(EXPERIMENT_DELIVERY_CONFIG_KEY, stored);
await expect(repository.getExperimentDeliveryConfig()).resolves.toEqual(DEFAULT_EXPERIMENT_DELIVERY_CONFIG);
});
it('round-trips a stored experiment delivery config', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
const config: ExperimentDeliveryConfig = {poll_interval_seconds: 900, poll_jitter_percent: 0};
await repository.setExperimentDeliveryConfig(config);
await expect(repository.getExperimentDeliveryConfig()).resolves.toEqual(config);
});
it('fills missing experiment delivery fields from the schema defaults', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(EXPERIMENT_DELIVERY_CONFIG_KEY, JSON.stringify({poll_interval_seconds: 3600}));
await expect(repository.getExperimentDeliveryConfig()).resolves.toEqual({
...DEFAULT_EXPERIMENT_DELIVERY_CONFIG,
poll_interval_seconds: 3600,
});
});
it('publishes a refresh so another repository observes the voice noise suppression config', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const reader = createRepository(kvProvider);
const writer = createRepository(kvProvider);
await expect(reader.getVoiceNoiseSuppressionConfig()).resolves.toEqual(DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG);
await writer.setVoiceNoiseSuppressionConfig({
...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
enabled: true,
config_version: 1,
});
await vi.waitFor(async () => {
expect(await reader.getVoiceNoiseSuppressionConfig()).toMatchObject({enabled: true, config_version: 1});
});
});
it('uses the registration URL id as the admin-visible registration code', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
@@ -6,6 +6,16 @@ import {
type GatewayRolloutConfig,
GatewayRolloutConfigSchema,
} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas';
import {
DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
type VoiceNoiseSuppressionConfig,
VoiceNoiseSuppressionConfigSchema,
} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas';
import {
DEFAULT_EXPERIMENT_DELIVERY_CONFIG,
type ExperimentDeliveryConfig,
ExperimentDeliveryConfigSchema,
} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
import type {IKVProvider, IKVSubscription} from '@pkgs/kv_client/src/IKVProvider';
import {Config} from '../Config';
import type {APIConfig, BlueskyOAuthConfig, BlueskyOAuthKeyConfig} from '../config/APIConfig';
@@ -21,6 +31,8 @@ import {getDefaultDateOfBirthCollection, setCachedDateOfBirthCollection} from '.
import {normalizeSsoAllowedEmailDomains} from './SsoConfigValidation';
const GATEWAY_ROLLOUT_CONFIG_KEY = 'gateway_rollout_config';
const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
const REGISTRATION_CONFIG_KEY = 'registration_config';
const REGISTRATION_URLS_KEY = 'registration_urls';
const REGISTRATION_PENDING_APPROVALS_KEY = 'registration_pending_approvals';
@@ -43,6 +55,20 @@ const DEFAULT_GATEWAY_ROLLOUT_CONFIG: GatewayRolloutConfig = {
gateway_dispatch_relay_max_queue: 50000,
voice_e2ee_scope: 'guild_feature_only',
};
function cloneDefaultVoiceNoiseSuppressionConfig(): VoiceNoiseSuppressionConfig {
return {
...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
enabled_backends: [...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG.enabled_backends],
included_user_ids: [...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG.included_user_ids],
excluded_user_ids: [...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG.excluded_user_ids],
guild_overrides: [...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG.guild_overrides],
};
}
function cloneDefaultExperimentDeliveryConfig(): ExperimentDeliveryConfig {
return {...DEFAULT_EXPERIMENT_DELIVERY_CONFIG};
}
export type InstanceRegistrationMode = 'open' | 'approval' | 'closed';
export interface InstanceRegistrationConfig {
mode: InstanceRegistrationMode;
@@ -1050,6 +1076,48 @@ export class InstanceConfigRepository {
await this.setConfig(GATEWAY_ROLLOUT_CONFIG_KEY, JSON.stringify(config));
}
async getVoiceNoiseSuppressionConfig(): Promise<VoiceNoiseSuppressionConfig> {
const raw = await this.getConfig(VOICE_NOISE_SUPPRESSION_CONFIG_KEY);
if (!raw) {
return cloneDefaultVoiceNoiseSuppressionConfig();
}
const parsed = parseJsonRecord(raw);
if (!parsed) {
return cloneDefaultVoiceNoiseSuppressionConfig();
}
const result = VoiceNoiseSuppressionConfigSchema.safeParse({...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG, ...parsed});
if (!result.success) {
Logger.error({error: result.error}, 'Invalid voice noise suppression config');
return cloneDefaultVoiceNoiseSuppressionConfig();
}
return result.data;
}
async setVoiceNoiseSuppressionConfig(config: VoiceNoiseSuppressionConfig): Promise<void> {
await this.setConfig(VOICE_NOISE_SUPPRESSION_CONFIG_KEY, JSON.stringify(config));
}
async getExperimentDeliveryConfig(): Promise<ExperimentDeliveryConfig> {
const raw = await this.getConfig(EXPERIMENT_DELIVERY_CONFIG_KEY);
if (!raw) {
return cloneDefaultExperimentDeliveryConfig();
}
const parsed = parseJsonRecord(raw);
if (!parsed) {
return cloneDefaultExperimentDeliveryConfig();
}
const result = ExperimentDeliveryConfigSchema.safeParse({...DEFAULT_EXPERIMENT_DELIVERY_CONFIG, ...parsed});
if (!result.success) {
Logger.error({error: result.error}, 'Invalid experiment delivery config');
return cloneDefaultExperimentDeliveryConfig();
}
return result.data;
}
async setExperimentDeliveryConfig(config: ExperimentDeliveryConfig): Promise<void> {
await this.setConfig(EXPERIMENT_DELIVERY_CONFIG_KEY, JSON.stringify(config));
}
async hasLimitConfig(): Promise<boolean> {
const raw = await this.getConfig('limit_config');
return raw !== null;