<?php

declare(strict_types=1);

$lane=(string)($argv[1]??'');$mode=(string)($argv[2]??'');
$self=$lane==='B'?(string)hash_file('sha256',__FILE__):'';
if(count($argv)!==3||!in_array($lane,['B','C'],true)||!in_array($mode,['--preflight','--execute','--audit'],true)
    ||($mode==='--execute'&&((string)getenv('CDX_GRW_APPROVE')!=='ROOT_GIFT_RULE_WRITE4_V1'
        ||($lane==='B'?!hash_equals($self,(string)getenv('CDX_GRW_SHA')):(string)getenv('CDX_GRW_SOURCE_CHECKED')!=='1')))){
    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())]),PHP_EOL;exit(2);});
$repo='/Users/wiscourper/Documents/workspace/zhct/zhctproject/store';
if($lane==='B'&&$mode==='--preflight'){$root=$repo;$pid=0;$port=0;}elseif($lane==='B'){$state='/tmp/cdx-rpe-b-worker.state';if(!is_file($state)||is_link($state)||(fileperms($state)&0777)!==0600)throw new RuntimeException('B worker state');$s=parse_ini_file($state,false,INI_SCANNER_RAW);$root=(string)($s['root']??'');$pid=(int)($s['pid']??0);$port=(int)($s['port']??0);if(!preg_match('~^/tmp/cdx-rpe-b-runtime\.[A-Za-z0-9]+$~D',$root)||$pid<=0||$port!==18082)throw new RuntimeException('B worker state invalid');}else{$root='/tmp/zhct-dm-poc-20260928.fcpiiN/store';$pid=112175;$port=18080;}
$hashes=['application/p/controller/AccountSetting.php'=>'65cf66aaa8866ec072841bcd4453d873f787d23c5a4fdb9036be9c3a7566fb66','application/p/logic/AccountSetting.php'=>'174036b5817358b659e086d9d4136e1bfc5bd3e8c75930e8e286c7341d6583e2','application/p/logic/AccountSettingPolicy.php'=>'385e3fa6a025d24a6cf8e7285e0a7f85990637116930b9024471378b87fd75ca','application/common/service/account/GiftAccountPolicy.php'=>'331a2216780de370872a3af897b18b51aa592e982e6b14fc035c3fb1c9a9a5e1','application/common/service/BaseService.php'=>'0d96474aa40a94abbb291a7730507e1980acf8a5d4f5ab0b8320beb538a637e0','application/base/AdminController.php'=>'7565b36fa4dda2c21cc4fa3b37753dbffb7c3a464bb8c11ea0f17ca00bececa5','application/base/Security.php'=>'b82e5ed982e6084a2b2942c033d4767428fb6f1012ba628f609477a7563eb4c3'];foreach($hashes as$p=>$sha)if(!is_file($root.'/'.$p)||hash_file('sha256',$root.'/'.$p)!==$sha)throw new RuntimeException('Source drift');
if($lane==='B'){$cfg=require$repo.'/application/config/local/database.php';$ai=$cfg['ai_zhct'];$dsn='mysql:host='.$ai['hostname'].';port='.($ai['hostport']??3306).';dbname='.$ai['database'].';charset='.($cfg['charset']??'utf8');$rule='yoshop_recharge_gift_rule';$log='yoshop_recharge_gift_rule_log';}
else{$ai=['username'=>(string)getenv('POC_AI_USER'),'password'=>(string)getenv('POC_AI_PASSWORD')];if((string)getenv('POC_AI_HOST')!=='127.0.0.1'||(string)getenv('POC_AI_PORT')!=='5238'||strtoupper((string)getenv('POC_AI_SCHEMA'))!=='AIZHCT_APP')throw new RuntimeException('C AI DB drift');$dsn='dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP';$rule='YOSHOP_RECHARGE_GIFT_RULE';$log='YOSHOP_RECHARGE_GIFT_RULE_LOG';}
$connect=static function()use($dsn,$ai):PDO{return new PDO($dsn,(string)$ai['username'],(string)$ai['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);};$db=$connect();
$rows=static function(PDO$p,string$sql,array$args=[],int$cap=2000):array{$s=$p->prepare($sql);$s->execute($args);$out=[];while(($r=$s->fetch(PDO::FETCH_ASSOC))!==false){if(count($out)>=$cap)throw new RuntimeException('row cap');$clean=[];foreach(array_change_key_case($r,CASE_LOWER)as$k=>$v)$clean[$k]=is_resource($v)?stream_get_contents($v):$v;ksort($clean);$out[]=$clean;}return$out;};$count=static function(PDO$p,string$sql,array$args=[])use($rows):int{$r=$rows($p,$sql,$args,1);return(int)(reset($r[0])?:0);};
$required=[$rule=>['RULE_ID','RULE_NAME','PAY_METHODS','MIN_MONEY','GIFT_MONEY','STATUS','STORE_ID','IS_DELETE','CREATE_BY','UPDATE_BY','CREATE_TIME','UPDATE_TIME'],$log=>['LOG_ID','RULE_ID','ACTION','BEFORE_SNAPSHOT','AFTER_SNAPSHOT','OPERATOR_ID','OPERATOR_NAME','STORE_ID','CREATE_TIME']];$meta=[];foreach($required as$table=>$columns){if($lane==='B'){$s=$db->prepare('SELECT COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION');$s->execute([$table]);}else{$s=$db->prepare('SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID');$s->execute([$table]);}$names=[];foreach($s->fetchAll(PDO::FETCH_ASSOC)as$r){$names[]=strtoupper((string)$r['column_name']);$meta[]=[$table,$r];}foreach($columns as$c)if(!in_array($c,$names,true))throw new RuntimeException('Required column absent');}
if($lane==='B'){$s=$db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (?,?)');$s->execute([$rule,$log]);$trigger=(int)$s->fetchColumn();$s=$db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE() AND (TABLE_NAME IN (?,?) OR REFERENCED_TABLE_NAME IN (?,?))');$s->execute([$rule,$log,$rule,$log]);$fk=(int)$s->fetchColumn();$refStmt=$db->query("SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND COLUMN_NAME IN ('rule_id','gift_rule_id')");}else{$trigger=(int)$db->query("SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME IN ('YOSHOP_RECHARGE_GIFT_RULE','YOSHOP_RECHARGE_GIFT_RULE_LOG')")->fetchColumn();$fk=(int)$db->query("SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND TABLE_NAME IN ('YOSHOP_RECHARGE_GIFT_RULE','YOSHOP_RECHARGE_GIFT_RULE_LOG')")->fetchColumn();$refStmt=$db->query("SELECT TABLE_NAME,COLUMN_NAME FROM USER_TAB_COLUMNS WHERE COLUMN_NAME IN ('RULE_ID','GIFT_RULE_ID')");}if($trigger||$fk)throw new RuntimeException('Unreviewed FK/trigger');$refColumns=$refStmt->fetchAll(PDO::FETCH_ASSOC);if(count($refColumns)>100)throw new RuntimeException('ref cap');
$natural=static function(PDO$p)use($rows,$rule,$log):string{$rules=$rows($p,'SELECT * FROM '.$rule." WHERE STORE_ID=10001 AND RULE_NAME NOT LIKE 'CDXGRW%' ORDER BY RULE_ID");$logs=$rows($p,'SELECT * FROM '.$log." WHERE STORE_ID=10001 AND OPERATOR_NAME NOT LIKE 'CDXGRW%' ORDER BY LOG_ID");return hash('sha256',serialize([$rules,$logs]));};$naturalSha=$natural($db);$markerCount=static function(PDO$p)use($count,$rule,$log):int{return$count($p,'SELECT COUNT(*) FROM '.$rule." WHERE RULE_NAME LIKE 'CDXGRW%' OR CREATE_BY LIKE 'CDXGRW%' OR UPDATE_BY LIKE 'CDXGRW%'")+$count($p,'SELECT COUNT(*) FROM '.$log." WHERE OPERATOR_ID LIKE 'CDXGRW%' OR OPERATOR_NAME LIKE 'CDXGRW%'");};$old=$markerCount($db);
if($mode==='--preflight'){echo json_encode(['status'=>$old===0?'write_ready_for_approval':'marker_stop','lane'=>$lane,'metadata_sha'=>hash('sha256',serialize($meta)),'foreign_keys'=>$fk,'triggers'=>$trigger,'rule_rows'=>$count($db,'SELECT COUNT(*) FROM '.$rule.' WHERE STORE_ID=10001'),'log_rows'=>$count($db,'SELECT COUNT(*) FROM '.$log.' WHERE STORE_ID=10001'),'reference_columns'=>count($refColumns),'old_marker_rows'=>$old,'natural_sha'=>$naturalSha,'http_sent'=>false,'dml'=>false]),PHP_EOL;exit($old===0?0:3);}
if($mode==='--audit'){$journal=(string)getenv('CDX_GRW_JOURNAL');if(!preg_match('~^/tmp/cdx-grw-[bc]-[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');$saved=json_decode((string)file_get_contents($journal),true);if(!is_array($saved)||($saved['lane']??null)!==$lane||($saved['phase']??null)!=='cleaned'||($saved['natural_sha']??null)!==$natural($connect())||$markerCount($connect())!==0)throw new RuntimeException('Audit mismatch');unlink($journal);rmdir(dirname($journal));echo json_encode(['status'=>'independent_zero_pass','lane'=>$lane,'marker_rows'=>0,'natural_unchanged'=>true,'private_journal_removed'=>true]),PHP_EOL;exit(0);}
if($old!==0)throw new RuntimeException('Old marker');$base='http://127.0.0.1:'.$port;$runtimeReady=static function()use($lane,$pid,$root,$port,$base):bool{if($lane==='B'){$cmd=(string)shell_exec('/bin/ps -p '.$pid.' -o command=');$owner=trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:'.$port.' -sTCP:LISTEN 2>/dev/null'));$ok=$owner===(string)$pid&&strpos($cmd,$root.'/public')!==false;}else{$cmd=(string)@file_get_contents('/proc/'.$pid.'/cmdline');$ok=strpos($cmd,$root.'/public')!==false&&trim((string)@file_get_contents('/proc/'.$pid.'/task/'.$pid.'/children'))==='';}$b=json_decode((string)@file_get_contents($base.'/__poc_boundary'),true);return$ok&&is_array($b)&&($b['external_stubs']??null)===true&&($b['redis_isolated']??null)===true&&($b['real_queue']??true)===false;};if(!$runtimeReady())throw new RuntimeException('HTTP boundary');
$marker='CDXGRW'.strtoupper(bin2hex(random_bytes(5)));$dir='/tmp/cdx-grw-'.strtolower($lane).'-'.strtolower(bin2hex(random_bytes(6)));if(!mkdir($dir,0700)||!chmod($dir,0700))throw new RuntimeException('Private dir');$journal=$dir.'/recovery.json';$saved=['lane'=>$lane,'marker'=>$marker,'phase'=>'before_http','rule_id'=>0,'natural_sha'=>$naturalSha];$save=static function()use(&$saved,$journal):void{$e=json_encode($saved);if(file_put_contents($journal,$e,LOCK_EX)!==strlen($e)||!chmod($journal,0600))throw new RuntimeException('Journal write');};$save();require_once$root.'/application/base/Security.php';$token=\app\base\Security::encrypt(implode("\t",['2147480021',$marker,$marker,'0','','',$marker]));$shape=null;$shape=static function($v)use(&$shape){if(!is_array($v))return gettype($v);if($v===[])return[];$keys=array_keys($v);if($keys===range(0,count($v)-1))return['[]'=>$shape($v[0])];$out=[];foreach($v as$k=>$x)$out[(string)$k]=$shape($x);ksort($out);return$out;};$evidence=static function(array$b)use($shape):array{return['key_sha'=>hash('sha256',serialize(array_keys($b))),'type_sha'=>hash('sha256',serialize($shape($b))),'shape'=>$shape($b)];};$uncertain=false;$request=static function(string$action,array$form,?string$token)use($base,$runtimeReady,&$uncertain):array{if(!in_array($action,['createGiftRule','updateGiftRule','changeGiftRuleStatus','deleteGiftRule'],true)||!$runtimeReady())throw new RuntimeException('HTTP boundary');$body=http_build_query($form,'','&',PHP_QUERY_RFC3986);$headers="Content-Type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($body)."\r\n".($token===null?'':'token: '.$token."\r\n");$uncertain=true;$stream=@fopen($base.'/p/AccountSetting/'.$action,'rb',false,stream_context_create(['http'=>['method'=>'POST','header'=>$headers,'content'=>$body,'timeout'=>20,'ignore_errors'=>true]]));$raw=is_resource($stream)?stream_get_contents($stream,1048577):false;$meta=is_resource($stream)?stream_get_meta_data($stream):[];$hs=$http_response_header??[];if(is_resource($stream))fclose($stream);preg_match('/HTTP\/\S+\s+(\d+)/',(string)($hs[0]??''),$m);$within=is_string($raw)&&strlen($raw)<=1048576;$json=$within?json_decode($raw,true):null;if(!isset($m[1])||(int)$m[1]!==200||!empty($meta['timed_out'])||!$within||!is_array($json)||json_last_error()!==JSON_ERROR_NONE||!array_key_exists('code',$json))throw new RuntimeException('HTTP unknown');$uncertain=false;return$json;};
$forms=['createGiftRule'=>['rule_name'=>$marker.'RULE','pay_methods'=>['cash','online_scan'],'min_money'=>'100.00','gift_money'=>'10.00','status'=>1]];$result=['status'=>'FAIL','lane'=>$lane,'cases'=>[],'rejections'=>[]];$failure=null;
try{foreach(['createGiftRule','updateGiftRule','changeGiftRuleStatus','deleteGiftRule']as$a){$unauth=$request($a,$a==='createGiftRule'?$forms[$a]:['rule_id'=>999999999,'status'=>2],null);if((int)($unauth['code']??0)===0)throw new RuntimeException('Unauth accepted');$result['rejections'][$a.'.unauth']=true;}$invalid=$request('createGiftRule',['rule_name'=>$marker.'BAD','pay_methods'=>[],'min_money'=>'100.00','gift_money'=>'10.00','status'=>1],$token);if((int)($invalid['code']??0)===0||$markerCount($connect())!==0)throw new RuntimeException('Invalid create accepted');$create=$request('createGiftRule',$forms['createGiftRule'],$token);if((int)($create['code']??-1)!==0||(int)($create['data']['rule_id']??0)<=0)throw new RuntimeException('Create failed');$id=(int)$create['data']['rule_id'];$saved['rule_id']=$id;$saved['phase']='created';$save();$owned=$rows($connect(),'SELECT * FROM '.$rule.' WHERE RULE_ID=? AND STORE_ID=10001 AND RULE_NAME=?',[$id,$marker.'RULE'],2);if(count($owned)!==1||(int)$owned[0]['status']!==1||(int)$owned[0]['is_delete']!==0||number_format((float)$owned[0]['min_money'],2,'.','')!=='100.00'||number_format((float)$owned[0]['gift_money'],2,'.','')!=='10.00')throw new RuntimeException('Create readback');$result['cases']['createGiftRule']=array_merge($evidence($create),['db_write'=>true]);$updateForm=['rule_id'=>$id,'rule_name'=>$marker.'UPDATED','pay_methods'=>['alipay_offline'],'min_money'=>'200.00','gift_money'=>'25.00','status'=>1];$update=$request('updateGiftRule',$updateForm,$token);if((int)($update['code']??-1)!==0)throw new RuntimeException('Update failed');$saved['phase']='updated';$save();$owned=$rows($connect(),'SELECT * FROM '.$rule.' WHERE RULE_ID=?',[$id],2);if(count($owned)!==1||(string)$owned[0]['rule_name']!==$marker.'UPDATED'||number_format((float)$owned[0]['min_money'],2,'.','')!=='200.00'||number_format((float)$owned[0]['gift_money'],2,'.','')!=='25.00')throw new RuntimeException('Update readback');$result['cases']['updateGiftRule']=array_merge($evidence($update),['same_rule_updated'=>true]);$status=$request('changeGiftRuleStatus',['rule_id'=>$id,'status'=>2],$token);if((int)($status['code']??-1)!==0)throw new RuntimeException('Status failed');$saved['phase']='disabled';$save();if($count($connect(),'SELECT COUNT(*) FROM '.$rule.' WHERE RULE_ID=? AND STATUS=2 AND IS_DELETE=0',[$id])!==1)throw new RuntimeException('Status readback');$result['cases']['changeGiftRuleStatus']=array_merge($evidence($status),['status_updated'=>true]);$delete=$request('deleteGiftRule',['rule_id'=>$id],$token);if((int)($delete['code']??-1)!==0)throw new RuntimeException('Delete failed');$saved['phase']='deleted';$save();if($count($connect(),'SELECT COUNT(*) FROM '.$rule.' WHERE RULE_ID=? AND IS_DELETE=1',[$id])!==1)throw new RuntimeException('Delete readback');$logs=$rows($connect(),'SELECT ACTION FROM '.$log.' WHERE RULE_ID=? AND OPERATOR_NAME=? ORDER BY LOG_ID',[$id,$marker],10);$actions=array_column($logs,'action');if($actions!==['create','update','disable','delete'])throw new RuntimeException('Audit log sequence');$result['cases']['deleteGiftRule']=array_merge($evidence($delete),['soft_deleted'=>true,'audit_log_count'=>4]);}catch(Throwable$e){$failure=['stage'=>$stage,'class'=>get_class($e),'message_sha'=>hash('sha256',$e->getMessage())];}
if($uncertain||!$runtimeReady()){$saved['phase']='unknown_http_retain';$save();echo json_encode(['status'=>'unknown_http_retain','journal'=>$journal]),PHP_EOL;exit(3);}if($saved['rule_id']>0){$id=(int)$saved['rule_id'];$contracts=['created'=>['actions'=>['create'],'name'=>$marker.'RULE','status'=>1,'is_delete'=>0],'updated'=>['actions'=>['create','update'],'name'=>$marker.'UPDATED','status'=>1,'is_delete'=>0],'disabled'=>['actions'=>['create','update','disable'],'name'=>$marker.'UPDATED','status'=>2,'is_delete'=>0],'deleted'=>['actions'=>['create','update','disable','delete'],'name'=>$marker.'UPDATED','status'=>2,'is_delete'=>1]];$phase=(string)$saved['phase'];if(!isset($contracts[$phase])){$saved['recovery_blocker']='unrecoverable_phase';$save();echo json_encode(['status'=>'unrecoverable_phase_retain','journal'=>$journal]),PHP_EOL;exit(4);}$contract=$contracts[$phase];$actualLogs=$rows($connect(),'SELECT ACTION FROM '.$log.' WHERE RULE_ID=? AND OPERATOR_NAME=? ORDER BY LOG_ID',[$id,$marker],10);if(array_column($actualLogs,'action')!==$contract['actions']){$saved['recovery_blocker']='action_prefix_mismatch';$save();echo json_encode(['status'=>'action_prefix_mismatch_retain','journal'=>$journal]),PHP_EOL;exit(4);}$refs=[];foreach($refColumns as$r){$table=(string)$r['table_name'];$column=(string)$r['column_name'];if(!preg_match('/^YOSHOP_[A-Z0-9_]+$/i',$table)||!preg_match('/^(RULE_ID|GIFT_RULE_ID)$/i',$column))throw new RuntimeException('Ref metadata');if(strcasecmp($column,'RULE_ID')===0&&strcasecmp($table,$rule)===0)continue;if(strcasecmp($column,'RULE_ID')===0&&strcasecmp($table,$log)!==0)continue;$n=$count($connect(),'SELECT COUNT(*) FROM '.$table.' WHERE '.$column.'=?',[$id]);if($n)$refs[strtoupper($table.'.'.$column)]=$n;}$expected=strtoupper($log.'.RULE_ID');$expectedLogs=count($contract['actions']);if(($refs[$expected]??0)!==$expectedLogs||count($refs)!==1){$saved['recovery_blocker']='unexpected_reference';$save();echo json_encode(['status'=>'unexpected_reference_retain','journal'=>$journal,'ref_sha'=>hash('sha256',serialize($refs))]),PHP_EOL;exit(4);}$owned=$count($connect(),'SELECT COUNT(*) FROM '.$rule.' WHERE RULE_ID=? AND RULE_NAME=? AND STORE_ID=10001 AND STATUS=? AND IS_DELETE=? AND UPDATE_BY=?',[$id,$contract['name'],$contract['status'],$contract['is_delete'],$marker]);if($owned!==1){$saved['recovery_blocker']='ownership_mismatch';$save();echo json_encode(['status'=>'ownership_mismatch_retain','journal'=>$journal]),PHP_EOL;exit(4);}$clean=$connect();$clean->beginTransaction();try{$s=$clean->prepare('DELETE FROM '.$log.' WHERE RULE_ID=? AND OPERATOR_NAME=?');$s->execute([$id,$marker]);if($s->rowCount()!==$expectedLogs)throw new RuntimeException('Log cleanup');$s=$clean->prepare('DELETE FROM '.$rule.' WHERE RULE_ID=? AND RULE_NAME=? AND STORE_ID=10001 AND STATUS=? AND IS_DELETE=? AND UPDATE_BY=?');$s->execute([$id,$contract['name'],$contract['status'],$contract['is_delete'],$marker]);if($s->rowCount()!==1)throw new RuntimeException('Rule cleanup');$clean->commit();}catch(Throwable$e){if($clean->inTransaction())$clean->rollBack();$saved['recovery_blocker']='cleanup_failed';$save();throw$e;}}
if($markerCount($connect())!==0||$natural($connect())!==$naturalSha)throw new RuntimeException('Cleanup/natural mismatch');$saved['phase']='cleaned';$save();$result['cleanup']=['marker_rows'=>0,'natural_unchanged'=>true,'journal'=>$journal];if($failure===null&&count($result['cases'])===4){$result['status']='HTTP_PASS_CLEANED_PENDING_AUDIT';echo json_encode($result,JSON_UNESCAPED_SLASHES),PHP_EOL;exit(0);}$result['status']='HTTP_FAIL_CLEANED';$result['failure']=$failure;echo json_encode($result,JSON_UNESCAPED_SLASHES),PHP_EOL;exit(2);
