instance('config',$config); $n=0;$ok=static function(bool $value,string $why)use(&$n){if(!$value)throw new RuntimeException($why);$n++;}; $reject=static function(callable $f)use($ok){try{$f();}catch(E $e){$ok(in_array($e->errorCode,['HTTPS_REQUIRED','CONFIG_INVALID'],true),'bounded configuration rejection');return;}throw new RuntimeException('Expected reject');}; $slot=['driver'=>'asr_then_llm','label'=>'Synthetic GPT','asr'=>['protocol'=>'dify','binding_revision'=>'test-v1','base_url'=>'https://asr.invalid/v1','api_key'=>'synthetic-asr','model'=>'synthetic-asr'],'extraction'=>['protocol'=>'openai','base_url'=>'http://text.invalid/v1','api_key'=>'synthetic-key','model'=>'synthetic-llm','http_base_opt_in'=>'http://text.invalid/v1']]; $s=['providers'=>['openai'=>$slot],'http_transport'=>'openssl_stream']; $p=P::resolve('openai',$s,[]);$ok(P::stageEndpointAllowed($p,'extraction'),'exact authorized text HTTP accepted');$ok(P::stageEndpointAllowed($p,'asr'),'ASR remains HTTPS'); $ok($p['extraction']['http_policy']==='exact-text-endpoint-v1','versioned opt-in included in fingerprint input'); foreach(['','http://other.invalid/v1','http://text.invalid:81/v1','http://text.invalid/other/v1','http://text.invalid/v1/','http://text.invalid/v1/chat/completions',true,[]]as$pin){$x=$s;$x['providers']['openai']['extraction']['http_base_opt_in']=$pin;$reject(fn()=>P::resolve('openai',$x,[]));} foreach(['http://text.invalid.evil/v1','http://text.invalid/v1?x=1','http://u:p@text.invalid/v1','http://text.invalid/a/../v1','http://text.invalid/%2e/v1',"http://text.invalid/v1\r\nX: 1",'ftp://text.invalid/v1']as$url){$x=$s;$x['providers']['openai']['extraction']['base_url']=$url;$x['providers']['openai']['extraction']['http_base_opt_in']=str_contains($url,'invalid.evil')?'http://text.invalid/v1':$url;$reject(fn()=>P::resolve('openai',$x,[]));} $x=$s;$x['providers']['openai']['asr']['base_url']='http://text.invalid/v1';$reject(fn()=>P::resolve('openai',$x,[])); $x['providers']['openai']['asr']['http_base_opt_in']='http://text.invalid/v1';$reject(fn()=>P::resolve('openai',$x,[])); $x=$s;$x['providers']['openai']['extraction']['protocol']='dify_chat';$x['providers']['openai']['extraction']['binding_revision']='test';$reject(fn()=>P::resolve('openai',$x,[])); $x=$s;$x['providers']['qwen']=$slot;unset($x['providers']['qwen']['extraction']['http_base_opt_in']);$reject(fn()=>P::resolve('qwen',$x,[])); $legacy=$slot;$legacy['extraction']['base_url']='https://text.invalid/v1';unset($legacy['extraction']['http_base_opt_in']);$old=['providers'=>['qwen'=>$legacy],'http_transport'=>'openssl_stream'];$oldHash=P::resolve('qwen',$old,[])['fingerprint'];$old['providers']['qwen']['extraction']['http_base_opt_in']='';$ok(P::resolve('qwen',$old,[])['fingerprint']===$oldHash,'empty opt-in does not change legacy hash');$old['providers']['openai']=$slot;$ok(P::resolve('qwen',$old,[])['fingerprint']===$oldHash,'new slot never rebinds old slot');$ok($p['fingerprint']!==$oldHash,'new HTTP identity differs'); $s['providers']['openai']['preview_verified_fingerprint']=$p['fingerprint'];$ok(P::previewVerified('openai',$s,[]),'explicit pin still needs matching preview fingerprint');$s['providers']['openai']['preview_verified_fingerprint']='';$ok(!P::previewVerified('openai',$s,[]),'no readiness invented');$ok(!P::secureEndpoint('http://text.invalid/v1',false),'global TLS primitive unchanged'); $dir=sys_get_temp_dir().'/fa-http-extraction-'.bin2hex(random_bytes(6));mkdir($dir,0700);$sock=stream_socket_server('tcp://127.0.0.1:0',$eno,$err);$port=(int)substr(strrchr(stream_socket_get_name($sock,false),':'),1);fclose($sock); file_put_contents($dir.'/router.php','['pipe','r'],1=>['file',$dir.'/stdout','a'],2=>['file',$dir.'/stderr','a']],$pipes,null,array_merge(getenv(),['LOG'=>$dir.'/requests']));fclose($pipes[0]); try{ for($i=0;$i<100;$i++){$sock=@stream_socket_client('tcp://127.0.0.1:'.$port,$eno,$err,.1);if($sock){fclose($sock);break;}usleep(20000);} $url='http://127.0.0.1:'.$port.'/v1/chat/completions';$spec=['url'=>$url,'stage'=>'extraction','timeout'=>3,'api_key'=>'synthetic-key','json'=>['model'=>'synthetic'],'http_extraction_url'=>$url,'extraction_protocol'=>'openai']; $reply=T::request($spec,fn()=>true,1024,false);$ok($reply['http_code']===200&&$reply['errno']===0&&$reply['body']==='{"ok":true}','real standalone stream child exact HTTP request without loopback bypass'); foreach(['missing','other-url','asr','dify','multipart']as$kind){$v=$spec;if($kind==='missing')unset($v['http_extraction_url']);if($kind==='other-url')$v['http_extraction_url']=$url.'/other';if($kind==='asr')$v['stage']='asr';if($kind==='dify')$v['extraction_protocol']='dify_chat';if($kind==='multipart')$v['multipart']=[];$before=file_get_contents($dir.'/requests');$reply=T::request($v,fn()=>true,1024,false);$ok($reply['errno']===43&&file_get_contents($dir.'/requests')===$before,'child rejects ' . $kind);} $spec['json']['redirect']=true;$reply=T::request($spec,fn()=>true,1024,false);$ok($reply['http_code']===302&&!str_contains(file_get_contents($dir.'/requests'),'/stolen/'),'redirect never followed'); echo 'FOLLOWUP_AUDIO_HTTP_EXTRACTION assertions='.$n.' PASS exact_pin=1 stage_isolation=1 legacy_fingerprint=1 child_transport=1 no_redirect=1'.PHP_EOL; }finally{proc_terminate($proc,9);proc_close($proc);foreach(glob($dir.'/*')as$f)unlink($f);rmdir($dir);}