Files
zyt/server/tests/AssistantSubmissionOwnershipR26bTest.php
2026-10-27 14:03:35 +08:00

64 lines
7.8 KiB
PHP

<?php
declare(strict_types=1);
namespace app\adminapi\service {
function hrtime(bool $number=false){$n=$GLOBALS['owner26Ns'];return $number?$n:[intdiv($n,1000000000),$n%1000000000];}
function usleep(int $us):void{$GLOBALS['owner26Ns']+=$us*1000;}
}
namespace {
require dirname(__DIR__).'/vendor/autoload.php';
use app\adminapi\service\AssistantRequestControl as C;
$checks=0;$number=0;$GLOBALS['owner26Ns']=100000000000;
$expect=static function(bool $ok,string $why)use(&$checks):void{++$checks;if(!$ok)throw new \RuntimeException($why);};
$dir=sys_get_temp_dir().'/r26b-submission-owner-'.bin2hex(random_bytes(8));
$make=static function()use($dir,&$number,$expect):array{$GLOBALS['owner26Ns']+=10000000000;$uuid=sprintf('c5678901-1234-4234-8234-%012d',++$number);$c=new C(7,11,$uuid,$dir);$expect($c->begin(),'Fresh synthetic owner begins.');return[$c,$dir.'/'.hash('sha256','7:11:'.$uuid).'.json',$uuid];};
$read=static fn(string $p):array=>json_decode(file_get_contents($p),true,64,JSON_THROW_ON_ERROR);
$write=static fn(string $p,array $s)=>file_put_contents($p,json_encode($s,JSON_THROW_ON_ERROR));
$never=static function():array{throw new \RuntimeException('Unbound lookup must not execute');};
try{
[$c,$path,$uuid]=$make();$token=$c->beginSubmission('qwen','owned-user');
$expect(preg_match('/\A[0-9a-f]{24}\z/D',$token)===1&&!$c->canStartAttempt(),'Opaque ownership is established before task discovery.');
try{$c->beginSubmission('qwen','owned-user');$expect(false,'Second admission must fail');}catch(\RuntimeException $e){$expect($e->getMessage()==='UPSTREAM_CLEANUP_PENDING','Two workers cannot both admit an unconfirmed submission.');}
$other=new C(7,11,'d5678901-1234-4234-8234-123456789abc',$dir);$expect($other->begin(),'Different UUID begins independently.');
$r=$other->closeSubmittedGeneration($token,'','rejected_before_execution',$never);$expect($r['reason']==='stale_submission'&&$c->publicState()['submitted_pending_attempt_count']===1,'Foreign UUID token cannot resolve an original attempt.');
$r=$c->closeSubmittedGeneration($token,'foreign-task','rejected_before_execution',$never);$expect($r['reason']==='stale_submission'&&$c->publicState()['submitted_transfer_closed_count']===0,'Wrong-task EOF cannot close a token.');
$c->closeSubmittedGeneration($token,'','unconfirmed',$never);
$c->closeSubmittedGeneration($token,'','rejected_before_execution',$never);
$expect(!$c->canStartAttempt()&&$read($path)['submitted_attempts'][$token]['disposition']==='unconfirmed','Duplicate EOF cannot upgrade unknown work to pre-execution rejection.');
$c->finish('done');$r=$c->reconcileClosedTasks($never);
$expect($r['status']==='pending'&&$r['queried_task_count']===0&&$r['upstream_pending_task_count']===1,'Full finish reports unknown pending with no guessed lookup.');
$p=$c->publicState();foreach([$token,$uuid,'owned-user']as$secret)$expect(!str_contains(json_encode($p),$secret),'Submission/EOF ownership token and scope remain private.');
[$c,$path]=$make();$token=$c->beginSubmission('qwen','owned-user');
foreach([['openai','owned-user'],['qwen','foreign-user']]as[$profile,$user]){
try{$c->bindTask($profile,$user,'own-task','own-run',$token);$expect(false,'Scope mismatch must fail');}catch(\RuntimeException $e){$expect($e->getMessage()==='UPSTREAM_TASK_IDENTITY_CONFLICT','Submission cannot migrate profile/user scope.');}
}
$c->bindTask('qwen','owned-user','own-task','',$token);$c->bindTask('qwen','owned-user','own-task','own-run',$token);
$state=$read($path);$expect($state['upstream']['generation']===1&&$state['submitted_attempts'][$token]['control_generation']===1,'Same original enrichment preserves local generation.');
$c->markTerminal('own-task','stopped','own-run');$calls=0;
$r=$c->closeSubmittedGeneration($token,'own-task','rejected_before_execution',static function(array $id,int $ms)use(&$calls,$expect):array{++$calls;$expect(array_keys($id)===['profile','user','task_id','workflow_run_id','generation'],'Opaque token is not added to adapter input or upstream request.');return ['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>'failed'];});
$state=$read($path);$expect($r['confirmed']&&$calls===1&&$c->canStartAttempt()&&$c->publicState()['submitted_pending_attempt_count']===0,'Known pair cannot use no-work waiver; exact GET still decides terminal.');
$expect($state['completed_generations'][1]['upstream']['submission_id']===$token&&$state['submitted_attempts'][$token]['disposition']==='unconfirmed','Original owned submission and differing terminal history survive archive.');
$next=$c->beginSubmission('qwen','owned-user');$c->closeSubmittedGeneration($token,'own-task','unconfirmed',$never);
$expect($next!==$token&&!$c->canStartAttempt()&&$c->publicState()['submitted_transfer_closed_count']===1,'Old EOF cannot close or resolve a newer admitted generation.');
$c->bindTask('qwen','owned-user','next-task','next-run',$next);
$r=$c->closeSubmittedGeneration($next,'next-task','unconfirmed',static function(array $id)use($path,$read,$write):array{$s=$read($path);$s['upstream']['submission_id']='replacement-owner';$write($path,$s);return ['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>'succeeded'];});
$expect($r['reason']==='stale_result'&&!isset($read($path)['persisted_run_status'])&&!$c->canStartAttempt(),'Same-pair verification cannot apply after opaque ownership changes.');
[$c,$path]=$make();$token=$c->beginSubmission('qwen','owned-user');$c->signalTimeout();$c->bindTask('qwen','owned-user','late-task','late-run',$token);
$expect(isset($read($path)['upstream'])&&!isset($read($path)['handoff_cleanup']),'Late first original metadata belongs to its already admitted transfer.');
$stops=0;$c->stopBoundTask(static function()use(&$stops):bool{++$stops;return true;},'late-task');
$expect($stops===1&&$c->publicState()['timeout_reached']&&!$c->publicState()['cancel_requested'],'Ordinary timeout retains coordinated stop without user cancellation.');
[$c,$path]=$make();$token=$c->beginSubmission('qwen','owned-user');$c->bindTask('qwen','owned-user','stop-task','stop-run',$token);$c->signalCancel();
$r=$c->stopBoundTask(static function()use($path,$read,$write):bool{$s=$read($path);$s['upstream']['submission_id']='replacement-owner';$write($path,$s);return true;},'stop-task');
$expect(!$r['accepted']&&!$c->publicState()['upstream_stop_confirmed'],'Stop result also cannot cross changed private submission ownership.');
foreach(['not_submitted','rejected_before_execution']as$disposition){
[$c,$path]=$make();$token=$c->beginSubmission('qwen','owned-user');$r=$c->closeSubmittedGeneration($token,'',$disposition,$never);$p=$c->publicState();
$expect(!$r['confirmed']&&!$r['pending']&&$c->canStartAttempt()&&!$p['upstream_terminal_confirmed'],'Explicit no-execution closure is not a fabricated terminal receipt.');
$expect($p['pre_execution_closed_attempt_count']===1&&$p['upstream_pending_task_count']===0&&$p['submitted_without_identity_count']===0,'Never-executed/rejected attempt counts differ from unidentified pending work.');
}
echo "R26b private submission ownership / stale EOF / claims: {$checks} assertions passed; synthetic controls only.\n";
}finally{$real=realpath($dir);$temp=realpath(sys_get_temp_dir());if($real!==false){if($temp===false||!str_starts_with(str_replace('\\','/',$real),rtrim(str_replace('\\','/',$temp),'/').'/r26b-submission-owner-'))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);}}
}