<?php

declare(strict_types=1);

$lane=(string)getenv('CDX_SR2_LANE');$mode=(string)($argv[1]??'');$self=(string)hash_file('sha256',__FILE__);
if(!in_array($lane,['B','C'],true)||count($argv)!==2||!in_array($mode,['--preflight','--execute','--audit'],true)
    ||($mode==='--execute'&&((string)getenv('CDX_SR2_APPROVE')!=='ROOT_STAFF_READ2_V1'||!hash_equals($self,(string)getenv('CDX_SR2_SHA'))))){
    echo json_encode(['status'=>'default_deny','connected'=>false,'http_sent'=>false,'dml'=>false]),PHP_EOL;exit(78);}
$stage='bootstrap';set_exception_handler(static function(Throwable$e)use(&$stage):void{echo json_encode(['status'=>'redacted_stop','stage'=>$stage,'class'=>get_class($e),'code'=>(string)$e->getCode(),'message_sha'=>hash('sha256',$e->getMessage()),'recovery'=>'inspect_private_journal_if_present']),PHP_EOL;exit(2);});
[$root,$pid,$port,$dialect]=['B'=>['/tmp/zhct-http-store-mysql.dQTTBP',10001,18081,'mysql'],'C'=>['/tmp/zhct-dm-poc-20260928.fcpiiN/store',112175,18080,'dm']][$lane];
$hashes=['/application/p/controller/Staff.php'=>'bfa0a3f7550e043d8cfc914aeca12fd17f3fe8acc92c7cf8369ff95e8db3c9e1','/application/p/logic/Staff.php'=>'6601c2deff891a4468b0aeefb5f435da94c4fd64ca5eef7185ee5245d424855b','/application/p/logic/MetabolismPal.php'=>'82b3ae37a57a9a09f2885a825543b823002d0dd9ddc54711caf75365566c0e28','/application/model/Staff.php'=>'e709b91af8e78758b0adbb29697dba4d16d62402d8c365712afe9c47e72277d3','/application/base/AdminController.php'=>'7565b36fa4dda2c21cc4fa3b37753dbffb7c3a464bb8c11ea0f17ca00bececa5'];
if(PHP_VERSION_ID<70300||PHP_VERSION_ID>=70400||is_link($root))throw new RuntimeException('Runtime/root drift');foreach($hashes as$path=>$sha)if(!is_file($root.$path)||hash_file('sha256',$root.$path)!==$sha)throw new RuntimeException('Business source drift');
$stage='http_boundary';$base='http://127.0.0.1:'.$port;$cmd=(string)@file_get_contents('/proc/'.$pid.'/cmdline');$childrenPath='/proc/'.$pid.'/task/'.$pid.'/children';$boundary=json_decode((string)@file_get_contents($base.'/__poc_boundary'),true);
if(strpos($cmd,$root.'/public')===false||strpos($cmd,'127.0.0.1:'.$port)===false||trim((string)@file_get_contents($childrenPath))!==''||!is_array($boundary)||($boundary['external_stubs']??null)!==true||($boundary['redis_isolated']??null)!==true||($boundary['real_queue']??true)!==false)throw new RuntimeException('HTTP boundary drift');
$stage='database_boundary';$runtime=require $root.'/application/database.php';if($dialect==='mysql'){$local=require '/workspace/zhct/zhctproject/store/application/config/local/database.php';foreach(['hostname','database','username','password']as$key)if(($runtime[$key]??null)!==($local[$key]??null))throw new RuntimeException('Not local MySQL');if((int)($runtime['hostport']??3306)!==(int)($local['hostport']??3306))throw new RuntimeException('Port drift');$dsn='mysql:host='.$runtime['hostname'].';port='.(int)($runtime['hostport']??3306).';dbname='.$runtime['database'].';charset='.(string)($runtime['charset']??'utf8');}
else{foreach(['hostname'=>'POC_HOST','hostport'=>'POC_PORT','database'=>'POC_SCHEMA','username'=>'POC_USER','password'=>'POC_PASSWORD']as$key=>$env)if((string)($runtime[$key]??'')!==(string)getenv($env))throw new RuntimeException('C config drift');if((string)getenv('POC_HOST')!=='127.0.0.1'||(string)getenv('POC_PORT')!=='5237'||strtoupper((string)getenv('POC_SCHEMA'))!=='ZHCT_APP')throw new RuntimeException('Not approved DM');$dsn='dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';}
$connect=static function()use($dsn,$runtime):PDO{return new PDO($dsn,(string)$runtime['username'],(string)$runtime['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER,PDO::ATTR_TIMEOUT=>5]);};$db=$connect();
$identity=$dialect==='mysql'?(string)$db->query("SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")->fetchColumn():'ZHCT_APP|5237|'.(string)$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();$approved=$dialect==='mysql'?'003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401':'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';if(hash('sha256',$identity)!==$approved)throw new RuntimeException('DB identity drift');
$p=$dialect==='dm'?'YDY_':'ydy_';$t=static function(string$n)use($p):string{if(!in_array($n,['user','user_code','staff','sport'],true))throw new RuntimeException('Unapproved table');return$p.$n;};
$fetch=static function(PDO$p,string$sql,array$args=[]):array{$q=$p->prepare($sql);$q->execute($args);return$q->fetchAll(PDO::FETCH_ASSOC);};$count=static function(PDO$p,string$sql,array$args=[]):int{$q=$p->prepare($sql);$q->execute($args);return(int)$q->fetchColumn();};$trim=static function($v):string{return rtrim((string)$v);};
$stage='source_scan';$groupSql='SELECT SPORT_UUID,SEX,COUNT(*) N FROM '.$t('staff')
    ." WHERE IS_SHOW=1 AND SPORT_UUID IS NOT NULL AND SPORT_UUID<>'' AND UUID NOT LIKE 'CDX%' AND NAME NOT LIKE 'CDX%'"
    .' AND SEX IN (1,2) GROUP BY SPORT_UUID,SEX ORDER BY N ASC';
$groups=$fetch($db,$dialect==='dm'?'SELECT * FROM ('.$groupSql.') WHERE ROWNUM<=20':$groupSql.' LIMIT 20');$candidate=null;$matchRows=[];
foreach($groups as$group){$sport=$trim($group['sport_uuid']);$sex=(int)$group['sex'];if(!preg_match('/^[A-Za-z0-9-]{1,64}$/D',$sport)||$sex<1||$sex>2)continue;
    $matches=$fetch($db,'SELECT NAME,UUID FROM '.$t('staff').' WHERE IS_SHOW=1 AND SPORT_UUID=? AND SEX=? AND UUID NOT LIKE ? AND NAME NOT LIKE ? ORDER BY ID',[$sport,$sex,'CDX%','CDX%']);
    if(count($matches)>=1&&count($matches)<=20000){$uuid=$trim($matches[0]['uuid']);$candidate=['uuid'=>$uuid,'sport_uuid'=>$sport,'sex'=>$sex];$matchRows=$matches;break;}}
$staffCount=$count($db,'SELECT COUNT(*) FROM '.$t('staff'));
$todayEnd=date('Y-m-d 23:59:59');$safeInfoCount=$count($db,'SELECT COUNT(*) FROM '.$t('staff').' S JOIN '.$t('user').' U ON U.STAFF_UUID=S.UUID AND U.IS_DELETE=0 JOIN '.$t('user_code').' C ON C.USER_ID=U.ID AND C.END_TIME=? WHERE S.IS_SHOW=1 AND C.PAYCODE IS NOT NULL'.($dialect==='dm'?'':" AND C.PAYCODE<>''"),[$todayEnd]);
$blankSportRows=$count($db,'SELECT COUNT(*) FROM '.$t('sport').($dialect==='dm'?" WHERE UUID IS NULL OR TRIM(UUID)=''":" WHERE UUID=''"));
$oldActors=$count($db,'SELECT COUNT(*) FROM '.$t('user')." WHERE ACCOUNT LIKE 'CDXSR2%' OR UUID LIKE 'CDXSR2%'");
if($dialect==='mysql'){$q=$db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?');$q->execute([$t('user')]);$triggers=(int)$q->fetchColumn();$q=$db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE() AND (TABLE_NAME=? OR REFERENCED_TABLE_NAME=?)');$q->execute([$t('user'),$t('user')]);$fks=(int)$q->fetchColumn();}else{$triggers=(int)$db->query("SELECT COUNT(*) FROM ALL_TRIGGERS WHERE OWNER='ZHCT_APP' AND TABLE_NAME='YDY_USER'")->fetchColumn();$fks=(int)$db->query("SELECT COUNT(*) FROM ALL_CONSTRAINTS WHERE OWNER='ZHCT_APP' AND CONSTRAINT_TYPE='R' AND TABLE_NAME='YDY_USER'")->fetchColumn();}if($triggers!==0||$fks!==0)throw new RuntimeException('Actor side-effect metadata');
$sourceSnapshot=static function(PDO$p,array$c)use($t,$fetch,$count):array{return['selected'=>$fetch($p,'SELECT UUID,NAME,SPORT_UUID,SPORT_EVENT_UUID,SEX,IS_SHOW FROM '.$t('staff').' WHERE UUID=?',[$c['uuid']]),'matches'=>$fetch($p,'SELECT NAME,UUID FROM '.$t('staff').' WHERE IS_SHOW=1 AND SPORT_UUID=? AND SEX=? AND UUID NOT LIKE ? AND NAME NOT LIKE ? ORDER BY ID',[$c['sport_uuid'],$c['sex'],'CDX%','CDX%']),'sport'=>$fetch($p,'SELECT * FROM '.$t('sport').' WHERE UUID=? ORDER BY ID',[$c['sport_uuid']]),'staff_count'=>$count($p,'SELECT COUNT(*) FROM '.$t('staff'))];};
$journal=(string)getenv('CDX_SR2_JOURNAL');if($mode==='--audit'){$stage='audit';if(!preg_match('~^/tmp/cdx-sr2-[a-f0-9]{12}/recovery\.json$~D',$journal)||!is_file($journal)||is_link($journal)||is_link(dirname($journal))||(fileperms(dirname($journal))&0777)!==0700||(fileperms($journal)&0777)!==0600)throw new RuntimeException('Journal invalid');$saved=json_decode((string)file_get_contents($journal),true);if(!is_array($saved)||($saved['lane']??null)!==$lane||($saved['phase']??null)!=='actor_deleted')throw new RuntimeException('Journal ownership invalid');$fresh=$connect();$q=$fresh->prepare('SELECT COUNT(*) FROM '.$t('user').' WHERE ID=? OR UUID=? OR ACCOUNT=?');$q->execute([(int)$saved['actor_id'],$saved['actor_uuid'],$saved['marker']]);if((int)$q->fetchColumn()!==0)throw new RuntimeException('Actor remains');if(hash('sha256',serialize($sourceSnapshot($fresh,$saved['candidate'])))!==(string)$saved['source_sha'])throw new RuntimeException('Source changed');unlink($journal);rmdir(dirname($journal));echo json_encode(['status'=>'independent_zero_pass','lane'=>$lane,'actor_rows'=>0,'source_unchanged'=>true,'private_journal_removed'=>true]),PHP_EOL;exit(0);}
if($mode==='--preflight'){$ready=is_array($candidate)&&$oldActors===0;echo json_encode(['status'=>$ready?'select_only_ready':'source_or_marker_stop','lane'=>$lane,'candidate_ready'=>is_array($candidate),'pal_match_rows'=>count($matchRows),'staff_count'=>$staffCount,'safe_info_today_rows'=>$safeInfoCount,'sport_blank_uuid_rows'=>$blankSportRows,'old_actor_rows'=>$oldActors,'actor_fk'=>$fks,'actor_triggers'=>$triggers,'source_sha'=>is_array($candidate)?hash('sha256',serialize($sourceSnapshot($db,$candidate))):null,'http_sent'=>false,'dml'=>false]),PHP_EOL;exit($ready?0:2);}if(!is_array($candidate)||$oldActors!==0)throw new RuntimeException('Source gate failed');
$marker='CDXSR2'.strtoupper(bin2hex(random_bytes(6)));$actorUuid=$marker.'U';$dir='/tmp/cdx-sr2-'.strtolower(bin2hex(random_bytes(6)));if(file_exists($dir)||!mkdir($dir,0700))throw new RuntimeException('Private directory unavailable');chmod($dir,0700);$journal=$dir.'/recovery.json';$source=$sourceSnapshot($connect(),$candidate);$sourceSha=hash('sha256',serialize($source));$saved=['lane'=>$lane,'marker'=>$marker,'actor_uuid'=>$actorUuid,'actor_id'=>0,'phase'=>'before_insert','candidate'=>$candidate,'source_sha'=>$sourceSha];$save=static function()use(&$saved,$journal):void{if(file_put_contents($journal,json_encode($saved),LOCK_EX)===false||!chmod($journal,0600))throw new RuntimeException('Journal write failed');};$save();$result=['status'=>'FAIL','lane'=>$lane,'cases'=>[]];$failure='';$uncertain=false;
try{$stage='actor_insert';$q=$db->prepare('INSERT INTO '.$t('user').' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,STATUS,CREATE_TIME,CREATE_BY,IS_DELETE) VALUES (?,?,?,?,?,0,0,?,?,0)');$q->execute([$actorUuid,$marker,'DISABLED',strtoupper(bin2hex(random_bytes(8))),$marker,date('Y-m-d H:i:s'),$marker]);$q=$db->prepare('SELECT ID FROM '.$t('user').' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');$q->execute([$actorUuid,$marker,$marker]);$ids=$q->fetchAll(PDO::FETCH_COLUMN);if(count($ids)!==1||(int)$ids[0]<=0)throw new RuntimeException('Actor ambiguous');$saved['actor_id']=(int)$ids[0];$saved['phase']='actor_inserted';$save();require_once $root.'/application/base/Security.php';$token=\app\base\Security::encrypt(implode("\t",[(string)$saved['actor_id'],$actorUuid,$marker,'0','','',$marker]));
    $send=static function(string$path,array$form,string$token='')use($base,&$uncertain):array{$headers="Content-Type: application/x-www-form-urlencoded\r\n".($token!==''?'token: '.$token."\r\n":'');$stream=@fopen($base.$path,'rb',false,stream_context_create(['http'=>['method'=>'POST','timeout'=>45,'ignore_errors'=>true,'header'=>$headers,'content'=>http_build_query($form,'','&',PHP_QUERY_RFC3986)]]));$raw=$stream===false?false:stream_get_contents($stream,4194305);$hs=$http_response_header??[];if(is_resource($stream))fclose($stream);preg_match('/HTTP\/\S+\s+(\d+)/',(string)($hs[0]??''),$m);$http=(int)($m[1]??0);if($stream===false||$raw===false||$http===0)$uncertain=true;return[$http,is_string($raw)?json_decode($raw,true):null];};
    $shape=static function($node)use(&$shape){if(!is_array($node))return gettype($node);if($node===[])return[];$keys=array_keys($node);if($keys===range(0,count($node)-1))return['[]'=>$shape($node[0])];$out=[];foreach($node as$k=>$v)$out[(string)$k]=$shape($v);ksort($out);return$out;};
    $paths=['palList'=>['/p/staff/palList',['sport_uuid'=>$candidate['sport_uuid'],'sex'=>$candidate['sex']]],'getCode'=>['/p/staff/getCode',[]]];
    $only=(string)getenv('CDX_SR2_ONLY');if($only!==''){if($only!=='getCode'||!isset($paths[$only]))throw new RuntimeException('Unsupported focused retest');$paths=[$only=>$paths[$only]];}
    foreach($paths as$name=>[$path,$form]){$stage='unauth_'.$name;[$http,$body]=$send($path,$form);if($http!==200||!is_array($body)||(int)($body['code']??0)===0)throw new RuntimeException('Unauthenticated route not rejected');}
    if(hash('sha256',serialize($sourceSnapshot($connect(),$candidate)))!==$sourceSha)throw new RuntimeException('Unauth changed source');
    if(isset($paths['palList'])){$stage='http_palList';[$http,$body]=$send('/p/staff/palList',$paths['palList'][1],$token);$rows=$body['data']??null;$found=null;if(is_array($rows))foreach($rows as$row)if($trim($row['uuid']??'')===$candidate['uuid']){$found=$row;break;}
        if($http!==200||(int)($body['code']??-1)!==0||!is_array($found)||count($rows)!==count($source['matches'])||$trim($found['name']??'')!==$trim($source['selected'][0]['name']))throw new RuntimeException('PAL source mismatch');
        $result['cases']['palList']=['source_match'=>true,'rows'=>count($rows),'key_sha'=>hash('sha256',serialize(array_keys($found))),'type_sha'=>hash('sha256',serialize($shape($found))),'selected_types'=>['name'=>gettype($found['name']??null),'uuid'=>gettype($found['uuid']??null)]];}
    if(isset($paths['getCode'])){$stage='http_getCode';[$http,$body]=$send('/p/staff/getCode',[],$token);$expected=101+1+$source['staff_count'];if(strlen((string)$expected)<4)$expected=substr('000'.(string)$expected,-4);
        if($http!==200||(int)($body['code']??-1)!==0||(string)($body['data']??'')!==(string)$expected)throw new RuntimeException('Code count mismatch');
        $result['cases']['getCode']=['source_match'=>true,'staff_count'=>$source['staff_count'],'key_sha'=>hash('sha256',serialize(array_keys($body))),'type_sha'=>hash('sha256',serialize($shape($body))),'selected_types'=>['code'=>gettype($body['code']??null),'message'=>gettype($body['message']??null),'data'=>gettype($body['data']??null)]];}
    $result['source_unchanged']=hash('sha256',serialize($sourceSnapshot($connect(),$candidate)))===$sourceSha;if(!$result['source_unchanged'])throw new RuntimeException('Source changed');$result['status']='HTTP_PASS_PENDING_CLEANUP';
}catch(Throwable$e){$failure=get_class($e).':'.(string)$e->getCode().':'.hash('sha256',$e->getMessage());$result['failure_stage']=$stage;}
if($uncertain||!is_array(json_decode((string)@file_get_contents($base.'/__poc_boundary'),true))||trim((string)@file_get_contents($childrenPath))!==''){$saved['phase']='uncertain_http_retain_actor';$save();echo json_encode(['status'=>'uncertain_http_retain_actor','lane'=>$lane,'journal'=>$journal],JSON_UNESCAPED_SLASHES),PHP_EOL;exit(3);} $stage='cleanup';$clean=$connect();$q=$clean->prepare('DELETE FROM '.$t('user').' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');$q->execute([$saved['actor_id'],$actorUuid,$marker,$marker]);if($q->rowCount()!==1)throw new RuntimeException('Actor cleanup mismatch');$saved['phase']='actor_deleted';$save();$result['cleanup']=['actor_rows'=>0,'journal'=>$journal];if($failure!==''){$result['status']='HTTP_FAIL_CLEANED';$result['failure']=$failure;}else$result['status']='HTTP_PASS_CLEANED_PENDING_AUDIT';echo json_encode($result,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),PHP_EOL;exit($failure===''?0:2);
