getMessage() === 'FOLLOWUP_AUDIO_DISABLED_OR_UNVERIFIED', 'Stable disabled error'); } try { Logic::requireEnabled(true); throw new RuntimeException('Expected public disabled gate'); } catch (DomainException $error) { $expect($error->getMessage() === '回访录音功能尚未启用', 'Public gate avoids database'); } $settings['followup_audio'] = ['enabled' => true, 'audio_verified' => true, 'verified_profiles' => ['qwen']]; $expect(!Store::verified(), 'Unbound flags alone never grant capability'); try { Logic::requireEnabled(true); throw new RuntimeException('Expected public unverified gate'); } catch (DomainException $error) { $expect(str_contains($error->getMessage(), '当前服务与模型') && !str_contains($error->getMessage(), 'Dify'), 'Public unverified error is provider-neutral'); } $expect(Providers::publicModels() === [], 'Unconfigured flags do not advertise models'); $provider = ['driver' => 'openai_audio', 'base_url' => 'https://synthetic.invalid/v1', 'api_key' => 'synthetic-test-only', 'model' => 'audio-fixture-v1', 'label' => 'Synthetic audio model']; $settings['followup_audio']['providers']['qwen'] = $provider; $fingerprint = Providers::resolve('qwen')['fingerprint']; $settings['followup_audio']['providers']['qwen']['verified_fingerprint'] = $fingerprint; $task = ['model_key' => 'qwen', 'upstream_ids_json' => json_encode(['provider_fingerprint' => $fingerprint]), 'upstream_started_at' => 0]; $expect(Store::verified('qwen') && Store::providerMatches($task), 'Exact approved provider matches bound task'); $expect(Providers::publicModels() === [['value' => 'qwen', 'label' => 'Synthetic audio model']], 'Public model list contains only configured value/label'); $public = json_encode(Providers::publicModels()); $expect(!str_contains($public, 'synthetic.invalid') && !str_contains($public, $provider['api_key']), 'Public labels never expose server URL/key'); foreach (['{}', 'not-json', '{"provider_fingerprint":null}', '{"provider_fingerprint":123}'] as $ids) { $unbound = array_replace($task, ['upstream_ids_json' => $ids]); $expect(!Store::providerMatches($unbound), 'Missing malformed or invalid fingerprint cannot be trusted'); } foreach (['driver' => 'dify', 'base_url' => 'https://changed.invalid/v1', 'api_key' => 'rotated-synthetic-key', 'model' => 'audio-fixture-v2'] as $field => $changed) { $settings['followup_audio']['providers']['qwen'] = $provider + ['verified_fingerprint' => $fingerprint]; $settings['followup_audio']['providers']['qwen'][$field] = $changed; $expect(!Store::verified('qwen') && !Store::providerMatches($task), 'Provider identity drift invalidates capability and task binding: ' . $field); } $expect(\think\facade\Db::$calls === 0, 'All missing-config and provider-binding checks performed without database I/O'); echo 'FOLLOWUP_AUDIO_PROVIDER_INTEGRATION assertions=' . $checks . " PASS database_calls=0 app_initialized=0 network_calls=0\n"; }