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

148 lines
15 KiB
PHP

<?php
declare(strict_types=1);
namespace PrivateTcmR26 {
function hrtime(bool $number=false): int { return \hrtime(true)+($GLOBALS['r26bReaderOffset']??0); }
function curl_init(): object { ++$GLOBALS['r26bReaderGets']; return (object)['options'=>[]]; }
function curl_setopt_array(object $h,array $options): bool { $h->options=$options; $GLOBALS['r26bReaderOptions'][]=$options; return true; }
function curl_exec(object $h): bool {
$run=basename($h->options[CURLOPT_URL]); $query=$GLOBALS['r26bReaderQueries'][$run]??null;
if($query===null)throw new \RuntimeException('Undeclared synthetic GET');
$body=json_encode(['id'=>$run,'status'=>'running','inputs'=>json_encode(['sys.query'=>$query],JSON_THROW_ON_ERROR)],JSON_THROW_ON_ERROR);
return ($h->options[CURLOPT_WRITEFUNCTION])($h,$body)===strlen($body);
}
function curl_errno(object $h): int { return 0; }
function curl_getinfo(object $h,int $which): int { return 200; }
function curl_close(object $h): void { ++$GLOBALS['r26bReaderClosed']; }
}
namespace {
require dirname(__DIR__).'/app/adminapi/service/AssistantRequestControl.php';
require dirname(__DIR__,2).'/app/artifacts/ai_concurrency/private_tcm_r26_wire_audit.php';
require dirname(__DIR__,2).'/app/artifacts/ai_concurrency/private_tcm_r26_run_input_inspector.php';
use app\adminapi\service\AssistantRequestControl as Control;
use PrivateTcmR26\RunInputInspector as Inspector;
$checks=0;$serial=0;$configCalls=0;$r26bReaderGets=0;$r26bReaderClosed=0;$r26bReaderQueries=[];$r26bReaderOptions=[];
$expect=static function(bool $ok,string $label)use(&$checks):void{++$checks;if(!$ok)throw new RuntimeException($label);};
$json=static fn($v):string=>json_encode($v,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR);
$base=sys_get_temp_dir().'/r26b-harness-reader-'.bin2hex(random_bytes(8));
$root=$base.'/server';$audits=$root.'/runtime/tcm-r26-wire-audits';$results=$base.'/app/artifacts/ai_concurrency';
mkdir($audits,0700,true);mkdir($results,0700,true);
$wire=static fn(string $q):array=>['protocol'=>'dify','body_sha256'=>hash('sha256','body'.$q),'body_bytes'=>strlen('body'.$q),
'query_sha256'=>hash('sha256',$q),'query_bytes'=>strlen($q),'inputs_sha256'=>hash('sha256','{}'),'inputs_bytes'=>2,'report_continuation_query'=>false];
$make=static function(array $modes=['terminal'],string $exit='natural_eof')use(&$serial,$base,$audits,$results,$wire,$json,$expect):array{
$n=++$serial;$uuid=sprintf('77777777-7777-4777-8777-%012d',$n);$label='owned';$directory=$base.'/controls'.$n;
$c=new Control(71,11,$uuid,$directory);$expect($c->begin(),'Fresh synthetic Control begins');
$path=$directory.'/'.hash('sha256','71:11:'.$uuid).'.json';
$a=new PrivateTcmR26WireAudit($audits,$uuid,$label.'_qwen','qwen',$path);$lookups=0;
foreach($modes as$i=>$mode){
$q="合成原始query\r\n末尾 é 😀".$n.'-'.$i;$task='own-task-'.$n.'-'.$i;$run='own-run-'.$n.'-'.$i;
$id=$mode==='unserialized'?null:$a->observe($wire($q));
if($mode!=='never_submitted'){$token=$c->beginSubmission('qwen','private-user');$a->submitted($token);$a->captureControl();}else $token='';
if(in_array($mode,['terminal','running','different_terminal'],true)){$id($task,$run);$c->bindTask('qwen','private-user',$task,$run,$token);$c->markTerminal($task,'stopped',$run);$a->captureControl($task);$GLOBALS['r26bReaderQueries'][$run]=$q;}
else $task='';
$a->transferClosedCurrent(['transport_closed_ms'=>1,'transport_exit'=>$exit],5.0+$i*20);
$disposition=match($mode){'never_submitted','not_submitted'=>'not_submitted','rejected'=>'rejected_before_execution',default=>'unconfirmed'};
if($token!=='')$c->closeSubmittedGeneration($token,$task,$disposition,static function(array $pair,int $ms)use(&$lookups,$mode,$expect):array{
++$lookups;$expect($ms>0&&$ms<=1000,'Real Control keeps 1s IO cap');
$expect(in_array($mode,['terminal','running','different_terminal'],true),'Unknown/pre-execution gets no guessed GET');
return ['ok'=>true,'workflow_run_id'=>$pair['workflow_run_id'],'status'=>$mode==='running'?'running':($mode==='different_terminal'?'succeeded':'stopped')];
});
$a->submissionClosed($token,$task,$disposition,6.0+$i*20);$a->captureControl();
if($i<count($modes)-1)$expect($c->canStartAttempt(),'Explicit no-execution admits finite fallback');
}
$c->finish('done');$a->captureControl();$state=$c->publicState();$summary=$a->finish($state);
$row=['harness_contract'=>'v43r26_private_run_wire','synthetic'=>true,'preparation_isolated'=>true,'preparation_isolation_verified'=>true,
'production_acceptance_path'=>true,'diagnostic_only'=>false,'case_id'=>11,'test_label'=>$label,'profile'=>'qwen',
'production_hashes'=>['fixture'=>'b54c47ec11f6a99dc87bdda78629e18c8a6f37a64017e42a72db399c727cb1c6','question'=>'28fbaee8585d4fbde3be3b1a6ee046c1a60f18a1fe67c0769211b08afdd5d306'],
'private_wire_audit'=>$summary,'private_wire_audit_verified'=>true,'cleanup_independently_confirmed'=>$summary['independent_cleanup_confirmed'],
'state'=>$state,'terminal_recovery_after_state'=>$state];
$doc=['harness_contract'=>'v43r26_private_run_wire','synthetic_only'=>true,'preparation_isolated'=>true,
'fixture_sha256'=>$row['production_hashes']['fixture'],'canonical_question_sha256'=>$row['production_hashes']['question'],'rows'=>[$row]];
$result=$results.'/tcm_complete_r26b_shape_'.$n.'.safe.json';file_put_contents($result,$json($doc));
return ['control'=>$path,'audit'=>$audits.'/'.hash('sha256',$uuid).'.private.jsonl','result'=>$result,'document'=>$doc,'summary'=>$summary,'state'=>$state,'lookup_count'=>$lookups];
};
$inspect=static function(array $f)use($root,&$configCalls):array{
return Inspector::inspectResultRow($root,['result_file'=>basename($f['result']),'test_label'=>'owned','profile'=>'qwen','audit_sha256'=>$f['summary']['audit_sha256']],
static function()use(&$configCalls):array{++$configCalls;return ['enable'=>true,'base_url'=>'https://synthetic.invalid/v1/chat-messages','models'=>['qwen'=>['api_key'=>'private-key-sentinel']]];});
};
try{
foreach(['natural_eof','timeout','local_abort','transport_error','exception']as$exit){
$f=$make(['terminal'],$exit);$s=$f['summary'];
$expect($s['persisted']&&$s['lifecycle_consistent']&&$s['independent_cleanup_confirmed'],'Real Control/audit terminal accounting');
$expect($s['generations'][0]['transfer_exit']===$exit,'Actual transport_exit key preserves all five enums');
$raw=file_get_contents($f['audit']);$expect(str_contains($raw,'"transfer_exit":"'.$exit.'"'),'Private exit enum preserved');
$before=[];foreach(['control','audit','result']as$k)$before[$k]=hash_file('sha256',$f[$k]);
$out=$inspect($f);$expect($out['ok']&&$out['inspected_run_count']===1&&$out['runs'][0]['run_status']==='running','Real distinct-clock full saved own audit reaches exact stub GET; returned running remains running');
$expect($out['saved_independent_cleanup_confirmed']&&!$out['authoritative_control_mutated'],'Fresh GET cannot promote or erase saved proof');
foreach($before as$k=>$h)$expect(hash_file('sha256',$f[$k])===$h,'Reader leaves exact synthetic evidence/control unchanged');
foreach(['own-task','own-run','private-user','private-key','77777777-','合成原始query','submission_id']as$v)$expect(!str_contains($json($out),$v),'Reader safe output excludes private pair/token/text');
$life=$s['generations'][0]['lifecycle'];$expect(abs($life['verification_started_at']-$life['transfer_closed_at'])>1000,'Production monotonic and wall clock origins actually differ');
}
$f=$make(['different_terminal']);$out=$inspect($f);
$expect($out['ok']&&$f['summary']['independent_cleanup_confirmed']&&$f['summary']['generations'][0]['lifecycle']['lifecycle_conflict'],'Different exact independently terminal enum resolves while disagreement stays latched');
$f=$make(['running']);$out=$inspect($f);$expect($out['ok']&&$out['saved_pending_generation_count']===1&&!$out['saved_independent_cleanup_confirmed'],'Complete closed pending footer is independently inspectable');
foreach(['unknown','unserialized','never_submitted','not_submitted','rejected']as$mode){
$f=$make([$mode]);$s=$f['summary'];$before=$r26bReaderGets;$cfg=$configCalls;$out=$inspect($f);
$expect($s['persisted']&&$s['lifecycle_consistent'],'Exact unknown/no-execution audit counts match real Control');
$expect(!$out['ok']&&$r26bReaderGets===$before&&$configCalls===$cfg&&$f['lookup_count']===0,'No original pair/query means no configuration or GET');
$noExecution=in_array($mode,['never_submitted','not_submitted','rejected'],true);
$expect($out['status']===($noExecution?'no_original_runs':($mode==='unserialized'?'original_query_unavailable':'original_pair_incomplete')),'Reader validates complete audit then explicitly refuses absent own original pair/query');
$expect($s['pending_generation_count']===($noExecution?0:1)&&$s['no_execution_generation_count']===(int)$noExecution,'Unknown execution stays pending; positive no-execution separately classified');
$expect($s['independently_terminal_generation_count']===0&&!$s['independent_cleanup_confirmed'],'No-execution never becomes independent persisted-terminal proof');
$expect($s['submitted_attempt_count']===($mode==='never_submitted'?0:1),'Never-submitted and owned admission are distinct');
$expect($s['serialized_request_count']===($mode==='unserialized'?0:1),'Unobserved blocking serialization never borrows prior query');
}
$f=$make(['rejected','terminal']);$out=$inspect($f);$s=$f['summary'];
$expect($out['ok']&&$out['inspected_run_count']===1,'Verified pre-execution rejection is skipped, exact fallback run only is read');
$expect($s['generation_count']===2&&$s['independently_terminal_generation_count']===1&&$s['no_execution_generation_count']===1&&$s['pending_generation_count']===0&&$s['independent_cleanup_confirmed'],'Rejected-before-execution fallback is resolved without false terminal count');
$life=$s['generations'][1]['lifecycle'];$validate=new ReflectionMethod(Inspector::class,'lifecycle');$bound=['closed'=>true,'snapshot'=>null];
$expect($validate->invoke(null,$life,$bound),'Real clock-domain snapshot accepted independently');
foreach(['wrong_domain','negative_start','negative_elapsed','late_proof','late_observation','pre_closure','attempt_cap','producer_after_closure']as$kind){
$bad=$life;
if($kind==='wrong_domain')$bad['clock_domains']['verification_started_at']='unix_seconds_microtime';
if($kind==='negative_start')$bad['verification_started_at']=-1;
if($kind==='negative_elapsed')$bad['verification_elapsed_ms']=-1;
if($kind==='late_proof')$bad['verification_elapsed_ms']=2001;
if($kind==='late_observation')$bad['observations'][0]['within_budget']=false;
if($kind==='pre_closure'){$bad['observations'][0]['observed_at']=$bad['transfer_closed_at']-1;$bad['persisted_run_observed_at']=$bad['observations'][0]['observed_at'];}
if($kind==='attempt_cap')$bad['verification_attempts']=3;
if($kind==='producer_after_closure')$bad['producer_terminal_observed_at']=$bad['transfer_closed_at']+1;
$expect(!$validate->invoke(null,$bad,$bound),'Explicit finite clocks/budget negative boundary '.$kind);
}
foreach(['foreign_token','duplicate_token','missing_submission','false_no_execution','changed_disposition','foreign_pair']as$kind){
$f=$make(['terminal']);$rows=array_map(static fn($line)=>json_decode($line,true,64,JSON_THROW_ON_ERROR),file($f['audit'],FILE_IGNORE_NEW_LINES));
$binding=null;$submissionState=null;
foreach($rows as$i=>$r){if($r['event']==='generation_submission')$binding=$i;if($r['event']==='generation_submission_state')$submissionState=$i;}
if($kind==='foreign_token')$rows[$submissionState]['submission_id']=str_repeat('f',24);
if($kind==='duplicate_token')array_splice($rows,$binding+1,0,[$rows[$binding]]);
if($kind==='missing_submission')array_splice($rows,$binding,1);
if($kind==='false_no_execution')$rows[$submissionState]['state']['disposition']='rejected_before_execution';
if($kind==='changed_disposition'){$extra=$rows[$submissionState];$extra['state']['disposition']='not_submitted';array_splice($rows,count($rows)-1,0,[$extra]);}
if($kind==='foreign_pair')$rows[$submissionState]['original_workflow_run_id']='foreign-run';
$raw=implode("\n",array_map($json,$rows))."\n";file_put_contents($f['audit'],$raw);
$f['summary']['audit_sha256']=hash('sha256',$raw);$f['summary']['audit_bytes']=strlen($raw);$f['document']['rows'][0]['private_wire_audit']=$f['summary'];file_put_contents($f['result'],$json($f['document']));
$before=$r26bReaderGets;$cfg=$configCalls;$out=$inspect($f);
$expect(!$out['ok']&&$r26bReaderGets===$before&&$configCalls===$cfg,'Submission/pair mutation fails before configuration/GET: '.$kind);
}
// Bounded optional read contention. Child owns only a new synthetic control.
$directory=$base.'/locked';mkdir($directory);$uuid='88888888-8888-4888-8888-888888888888';
$c=new Control(71,11,$uuid,$directory);$expect($c->begin(),'Contention synthetic control starts');$path=$directory.'/'.hash('sha256','71:11:'.$uuid).'.json';$state=$c->publicState();$hash=hash_file('sha256',$path);
$a=new PrivateTcmR26WireAudit($audits,$uuid,'lock_qwen','qwen',$path);
$code='$h=fopen($argv[1],"rb");flock($h,LOCK_EX);echo "locked\\n";fflush(STDOUT);usleep(350000);flock($h,LOCK_UN);fclose($h);';
$process=proc_open([PHP_BINARY,'-r',$code,$path],[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes,null,null,['bypass_shell'=>true]);
if(!is_resource($process))throw new RuntimeException('Synthetic lock child unavailable');
$expect(trim(fgets($pipes[1]))==='locked','Synthetic child owns lock before measurement');
$start=hrtime(true);$a->captureControl();$elapsed=(hrtime(true)-$start)/1e6;$s=$a->finish($state);
foreach($pipes as$pipe)fclose($pipe);$expect(proc_close($process)===0,'Synthetic child exits');
$expect($elapsed<150&&!$s['persisted']&&!$s['independent_cleanup_confirmed'],'LOCK_SH|LOCK_NB makes optional contention immediately unavailable');
$expect(hash_file('sha256',$path)===$hash,'Contention never mutates authoritative control');
$expect($r26bReaderGets===$r26bReaderClosed,'All stub handles close');
echo $json(['optional_lock_capture_elapsed_ms'=>$elapsed,'synthetic_lock_child_exit'=>0,'stub_get_count'=>$r26bReaderGets,'stub_gets_all_closed'=>$r26bReaderGets===$r26bReaderClosed,'actual_network_or_model_calls'=>0])."\n";
echo "r26b production-shaped harness/reader: {$checks} checks passed; real Control clocks, synthetic lookup/GET only, no auth/bootstrap/network/model.\n";
}finally{
$real=realpath($base);$temp=realpath(sys_get_temp_dir());
if($real!==false){if($temp===false||!str_starts_with(str_replace('\\','/',$real),rtrim(str_replace('\\','/',$temp),'/').'/r26b-harness-reader-'))throw new RuntimeException('Cleanup boundary');
$it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST);
foreach($it as$f){if($f->isLink())throw new RuntimeException('Unexpected symlink');$f->isDir()?rmdir($f->getPathname()):unlink($f->getPathname());}rmdir($real);}
}
}