true];} } } namespace app\common\service { class DifyWorkflowRunStatus { public static function lookup(array $id,int $ms):array{ ++$GLOBALS['r26FinalGets'];return ($GLOBALS['r26FinalLookup'])($id,$ms); } } } namespace think\facade { class Log {public static function write(string $message,string $level=''):void{if(!empty($GLOBALS['r26FinalLogThrows']))throw new \RuntimeException('fixture log failure');}} } namespace { require dirname(__DIR__).'/app/adminapi/service/AssistantRequestControl.php'; require dirname(__DIR__).'/app/adminapi/controller/tcm/DiagnosisController.php'; use app\adminapi\service\AssistantRequestControl as Control; use app\adminapi\controller\tcm\DiagnosisController; $checks=0;$number=0;$GLOBALS['r26FinalNs']=100000000000;$GLOBALS['r26FinalGets']=0; $expect=static function(bool $ok,string $why)use(&$checks):void{++$checks;if(!$ok)throw new \RuntimeException($why);}; $directory=sys_get_temp_dir().'/r26-final-'.bin2hex(random_bytes(8)); $make=static function()use($directory,&$number,$expect):array{ $GLOBALS['r26FinalNs']+=10000000000;$uuid=sprintf('e2345678-1234-4234-8234-%012d',++$number); $c=new Control(7,11,$uuid,$directory);$expect($c->begin(),'Synthetic request begins.');$c->bindTask('qwen','fixture-user','fixture-task','fixture-run'); return [$c,$directory.'/'.hash('sha256','7:11:'.$uuid).'.json']; }; $method=new \ReflectionMethod(DiagnosisController::class,'finishAssistantError');$controller=new DiagnosisController(); $emit=static function(array &$events):callable{return static function(string $event,array $payload)use(&$events):bool{$events[]=[$event,$payload];return true;};}; try { foreach(['ordinary_timeout','cancelled']as$intent)foreach(['running','stopped','succeeded','failed']as$status){ [$c,$path]=$make();$cancelled=$intent==='cancelled';if($cancelled)$c->signalCancel();else$c->signalTimeout(); $c->markTerminal('fixture-task','stopped','fixture-run');$before=$GLOBALS['r26FinalGets']; $GLOBALS['r26FinalLookup']=static function(array $id,int $ms)use($c,$expect,$status):array{ $expect($c->publicState()['transport_closed'],'Full-request recovery occurs after finish.'); $expect($ms>0&&$ms<=1000,'Original bounded final adapter budget.'); return ['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>$status]; }; $events=[];$method->invoke($controller,$emit($events),$c,$cancelled);$last=end($events)[1]; $expect($last['code']===($cancelled?'AI_ASSISTANT_CANCELLED':'UPSTREAM_TIMEOUT') && $last['message']===($cancelled?'本次请求已取消':'original fixture error'),'Independent recovery preserves original error/cancel outcome and exact message.'); $expect(count($events)===($cancelled?2:1) && $last['cancel_requested']===$cancelled && $last['timeout_reached']===!$cancelled,'Ordinary timeout never becomes user cancel; original progress intent remains.'); $expect($GLOBALS['r26FinalGets']-$before===($status==='running'?2:1),'Only bounded exact original run lookups occur.'); $expect($last['producer_terminal_status']==='stopped' && $last['persisted_run_status']===$status && $last['upstream_pending_task_count']===($status==='running'?1:0),'Final error receipt reports producer and independent status honestly.'); $expect($last['upstream_terminal_confirmed']===($status!=='running') && $last['lifecycle_conflict']===($status!=='stopped'),'Terminal source is independent GET; contrary receipt history stays visible.'); $expect(!str_contains(json_encode($events),'fixture-task')&&!str_contains(json_encode($events),'fixture-run')&&!str_contains(json_encode($events),'fixture-user'),'Final client error/progress contain no original private identities.'); if($status==='running')$expect(isset(json_decode(file_get_contents($path),true)['upstream']),'Full-request finish retains conflicting original pair.'); } [$c,$path]=$make();$c->markTerminal('fixture-task','stopped','fixture-run'); $GLOBALS['r26FinalLookup']=static fn(array $id):array=>['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>'running']; $c->closeGeneration('fixture-task',[\app\common\service\DifyWorkflowRunStatus::class,'lookup']);$before=$GLOBALS['r26FinalGets']; $events=[];$method->invoke($controller,$emit($events),$c,false); $expect($GLOBALS['r26FinalGets']===$before && end($events)[1]['upstream_pending_task_count']===1,'Final recovery cannot renew a generation already verified at EOF.'); foreach(['adapter_throws','log_throws','corrupt_control']as$failure){ [$c,$path]=$make();$c->signalTimeout();$c->markTerminal('fixture-task','stopped','fixture-run'); $GLOBALS['r26FinalLogThrows']=$failure==='log_throws'; $GLOBALS['r26FinalLookup']=static function(array $id)use($failure,$path):array{ if($failure==='adapter_throws')throw new \RuntimeException('fixture private adapter prose'); if($failure==='corrupt_control')file_put_contents($path,'fixture corrupt state'); return ['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>'failed']; }; $events=[];$method->invoke($controller,$emit($events),$c,false);$last=end($events)[1]; $expect($last['code']==='UPSTREAM_TIMEOUT' && $last['message']==='original fixture error' && $last['retryable'],'Recovery/telemetry/state failure never replaces original application error.'); $expect(!str_contains(json_encode($events),'private adapter prose')&&!str_contains(json_encode($events),'corrupt state'),'Failure prose is discarded.'); if($failure==='corrupt_control')$expect($last===['code'=>'UPSTREAM_TIMEOUT','message'=>'original fixture error','retryable'=>true],'Unavailable state does not fabricate a cleanup receipt.'); } echo "R26 real controller final error/recovery: {$checks} assertions passed (synthetic controls and adapter only).\n"; } finally { $real=realpath($directory);$temp=realpath(sys_get_temp_dir()); if($real!==false){if($temp===false||!str_starts_with(str_replace('\\','/',$real),rtrim(str_replace('\\','/',$temp),'/').'/r26-final-'))throw new \RuntimeException('Cleanup boundary'); foreach(new \DirectoryIterator($real)as$f){if($f->isDot())continue;if($f->isLink()||!$f->isFile())throw new \RuntimeException('Unexpected temp entry');unlink($f->getPathname());}rmdir($real);} } }