<?php
declare(strict_types=1);
require __DIR__.'/bootstrap.php';
if(($argv[1]??'')==='audit'){echo json_encode(['lane'=>$lane,'owned_counts'=>$counts()]),"\n";exit(array_sum($counts())?1:0);}
$check(array_sum($counts())===0,'Config collision before DML');$before=$snapshot();$started=time();
$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);$check(($boundary['allowlist']??'')==='grade-plan-config-only'&&!empty($boundary['all_external_calls_forbidden']),'Actual private config boundary');
$check(($boundary['plan_serializer_sha']??'')==='d61d53d6c13d516698eacbad14d21f3032f5ac7e206a31f03412e3348e4f3a5a','Actual loaded Plan serializer source SHA');
$root=(string)getenv('CATEGORY_ROOT');define('TP_ENV','poc');define('BUSINESS','aizhct');define('ENTERPRISE_NO','10001');require $root.'/vendor/autoload.php';
$app=new \think\App($root);$app->setEnvName('poc');$app->setRuntimePath($runtime.'/fixture-runtime/');$app->initialize();
$cache=new \think\cache\driver\File($app,['path'=>$runtime.'/cache/','prefix'=>'','expire'=>0]);$tokens=[];
foreach($tenants as $module=>$tenant){$tokens[$module]=bin2hex(random_bytes(24));$cache->set($tokens[$module],['user'=>['store_user_id'=>$tenant,'store_id'=>$tenant,'real_name'=>$marker,'user_name'=>$marker,'is_super'=>1],'store_id'=>$tenant,'is_login'=>true],1800);}
$map=['rows'=>[]];file_put_contents($runtime.'/private-owned-map.json',json_encode($map));file_put_contents($runtime.'/queries.jsonl','');file_put_contents($runtime.'/write-audit.jsonl','');file_put_contents($runtime.'/private-write-sql.jsonl','');
$cases=[];$proofs=[];$idMap=[];$times=[];$privateResponses=[];$error=null;
$deep=static function($a,$b,string $path='$')use(&$deep,$check):void{if(is_array($b)){$check(is_array($a),'Array '.$path);$ak=array_keys($a);$bk=array_keys($b);sort($ak);sort($bk);$check($ak===$bk,'Full fields '.$path);foreach($b as $key=>$v)$deep($a[$key],$v,$path.'.'.$key);}else $check($a===$b,'Value/type '.$path);};
$shape=static function($v)use(&$shape){if(is_object($v)){$o=[];foreach(get_object_vars($v) as $k=>$x)$o[$k]=$shape($x);ksort($o);return ['object'=>$o];}if(is_array($v))return ['array'=>array_map($shape,$v)];return gettype($v);};
$tableFor=['grade'=>'user_grade','plan'=>'recharge_plan'];$pathFor=['grade'=>'user.Grade','plan'=>'market.recharge.Plan'];
$request=static function(string $module,string $kind,string $name,string $action,array $args=[],?array $form=null)use($base,$tokens,$pathFor,$shape,$check,&$privateResponses):array{
    $scenario='PILOT_GRC_'.strtoupper($module.'_'.$kind.'_'.$name);$method=in_array($action,['list','all'],true)?'GET':'POST';$url=$base.'/'.$module.'/'.$pathFor[$kind].'/'.$action;
    if($method==='GET'&&$args)$url.='?'.http_build_query($args);$payload=$args;if($form!==null)$payload['form']=$form;
    $headers='Host: '.$module.".grade-plan.invalid\r\nAccess-Token: ".$tokens[$module]."\r\nPlatform: POC\r\nX-Pilot-Scenario: ".$scenario."\r\nContent-Type: application/json\r\n";
    $raw=file_get_contents($url,false,stream_context_create(['http'=>['method'=>$method,'header'=>$headers,'content'=>$method==='GET'?'':json_encode($payload,JSON_UNESCAPED_UNICODE),'timeout'=>30,'ignore_errors'=>true]]));
    $check(is_string($raw)&&strlen($raw)<1048576,'HTTP transport/size');$body=json_decode($raw,true);$check(is_array($body),'HTTP JSON '.$scenario);preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);
    $privateResponses[$scenario]=$raw;return ['scenario'=>$scenario,'method'=>$method,'path'=>'/'.$module.'/'.$pathFor[$kind].'/'.$action,'http_status'=>(int)($m[1]??0),'body'=>$body,'raw_json_shape'=>$shape(json_decode($raw))];
};
$expect=static function(array $case,int $status=200,string $message='')use($check):void{$check($case['http_status']===200&&($case['body']['status']??null)===$status,'Business status '.$case['scenario']);if($message!=='')$check($case['body']['message']===$message,'Business message '.$case['scenario']);};
$project=static function(string $kind,array $row,bool $wireTimes=true)use($check,$started,&$times):array{
    $pk=$kind==='grade'?'grade_id':'plan_id';$id=(int)$row[$pk];$out=$row;
    foreach($out as $key=>&$v){if(in_array($key,[$pk,'weight','status','is_delete','store_id','sort'],true))$v=(int)$v;
        elseif(in_array($key,['upgrade','equity'],true)){$v=json_decode((string)$v,true);$check(json_last_error()===JSON_ERROR_NONE,'Full CLOB JSON '.$key);}
        elseif(in_array($key,['money','gift_money'],true))$v=bcadd((string)$v,'0',2);
        elseif(in_array($key,['create_time','update_time'],true)){$n=(int)$v;$check($n>=$started-2&&$n<=time()+2,'Auto time execution window '.$key);$v=$wireTimes?date('Y-m-d H:i:s',$n):$n;}}
    unset($v);$previous=$times[$kind][$id]??null;if($previous!==null)$check((int)$row['create_time']===$previous[0]&&(int)$row['update_time']>=$previous[1],'Original auto time invariants');
    $times[$kind][$id]=[(int)$row['create_time'],(int)$row['update_time']];return $out;
};
$ownRow=static function(string $module,string $kind,string $name,array $desired,?int $id=null)use($query,$check,$tableFor,$tables,$tenants,$marker,$project,&$map,&$idMap,$runtime,&$proofs):array{
    $table=$tableFor[$kind];$pk=$tables[$table];$nameColumn=$kind==='grade'?'name':'plan_name';$where=$id===null?$nameColumn.'=?':$pk.'=?';$rows=$query('SELECT * FROM yoshop_'.$table.' WHERE '.$where.' AND store_id=?',[$id===null?$name:$id,$tenants[$module]]);
    $check(count($rows)===1&&strpos($rows[0][$nameColumn],$marker.'_'.$module)===0,'Original owned config');$row=$rows[0];$actual=$project($kind,$row,false);
    foreach($desired as $key=>$v){if($kind==='grade'&&$key==='sort'){$check(!array_key_exists('sort',$actual),'Grade unknown sort must filtered');continue;}
        if(in_array($key,['money','gift_money'],true))$check($actual[$key]===$v,'Physical decimal expected '.$key);else $check($actual[$key]===$v,'Original input/full JSON '.$key);}
    $rid=(int)$row[$pk];$check($rid>0,'Positive generated config PK');$map['rows']['yoshop_'.$table][(string)$rid]=$tenants[$module];
    if(!isset($idMap[$kind][$rid]))$idMap[$kind][$rid]=count($idMap[$kind]??[])+1;
    file_put_contents($runtime.'/private-owned-map.json',json_encode($map));chmod($runtime.'/private-owned-map.json',0600);
    $normalized=$actual;$normalized[$pk]=$idMap[$kind][$rid];$normalized['create_time']='VERIFIED_OWN_CREATE_TIME';$normalized['update_time']='VERIFIED_OWN_UPDATE_TIME';
    $proofs[]=['module'=>$module,'kind'=>$kind,'all_requested_fields_and_json_values_verified'=>true,'owned_primary_key_and_store_verified'=>true,'original_values'=>$normalized];return $row;
};
$read=static function(string $module,string $kind,string $name,string $action,array $args=[],?int $want=null)use($request,$expect,$query,$check,$deep,$tableFor,$tables,$tenants,$project,&$idMap,&$cases):void{
    $case=$request($module,$kind,$name,$action,$args);$expect($case);$table=$tableFor[$kind];$pk=$tables[$table];$where='store_id=? AND is_delete=0';$values=[$tenants[$module]];
    if($kind==='grade'&&isset($args['status'])&&(int)$args['status']>-1){$where.=' AND status=?';$values[]=(int)$args['status'];}
    if($kind==='plan'&&!empty($args['search'])){$where.=' AND plan_name LIKE ?';$values[]='%'.$args['search'].'%';}
    $rows=$query('SELECT * FROM yoshop_'.$table.' WHERE '.$where.' ORDER BY '.($kind==='grade'?'weight':'sort').','.$pk,$values);
    $page=(int)($args['page']??1);$expectedRows=$action==='all'?$rows:array_slice($rows,($page-1)*15,15);
    $expected=array_map(static function($row)use($kind,$project){return $project($kind,$row);},$expectedRows);if($want!==null)$check(count($expected)===$want,'Expected source row cardinality');
    $last=(int)ceil(count($rows)/15);$reportedPage=$page>$last?($last?:1):$page;
    $expectedList=$action==='all'?$expected:['total'=>count($rows),'per_page'=>15,'current_page'=>$reportedPage,'last_page'=>$last,'data'=>$expected];
    $deep($case['body']['data'],['list'=>$expectedList]);$returned=&$case['body']['data']['list'];if($action==='list')$returned=&$returned['data'];
    foreach($returned as &$row){$rid=$row[$pk];$check(is_int($rid)&&isset($idMap[$kind][$rid]),'Full wire owned integer mapping');$row[$pk]=$idMap[$kind][$rid];$row['create_time']='VERIFIED_OWN_CREATE_TIME';$row['update_time']='VERIFIED_OWN_UPDATE_TIME';}unset($row,$returned);
    $case['independent_original_projection_equal']=true;$case['returned_rows']=count($expected);$cases[]=$case;
};
try{
foreach($tenants as $module=>$tenant){
    $baseName=$marker.'_'.$module;
    $gradeA=['name'=>$baseName.'_等级\'甲','weight'=>1,'upgrade'=>['expend_money'=>100.55],'equity'=>['discount'=>'9.8'],'status'=>1,'sort'=>100];
    $gradeB=['name'=>$baseName.'_等级乙','weight'=>2,'upgrade'=>['expend_money'=>'200.00'],'equity'=>['discount'=>8.5],'status'=>0,'sort'=>200];
    foreach([['A',$gradeA],['B',$gradeB]] as $item){$case=$request($module,'grade','ADD_'.$item[0],'add',[],$item[1]);$cases[]=$case;$expect($case,200,'添加成功');$r=$ownRow($module,'grade',$item[1]['name'],$item[1]+['is_delete'=>0]);if($item[0]==='A')$ga=(int)$r['grade_id'];else $gb=(int)$r['grade_id'];}
    $beforeReject=$query('SELECT * FROM yoshop_user_grade WHERE store_id=? ORDER BY grade_id',[$tenant]);$duplicate=$gradeA;$duplicate['name']=$baseName.'_重复权重';
    $case=$request($module,'grade','DUPLICATE_WEIGHT_ADD','add',[],$duplicate);$cases[]=$case;$expect($case,500,'等级权重已存在');$check($beforeReject===$query('SELECT * FROM yoshop_user_grade WHERE store_id=? ORDER BY grade_id',[$tenant]),'Duplicate add changed rows');
    $read($module,'grade','LIST_NONEMPTY','list',[],2);$read($module,'grade','ALL_ENABLED','all',['status'=>1],1);
    $duplicate=$gradeB;$duplicate['weight']=1;$case=$request($module,'grade','DUPLICATE_WEIGHT_EDIT','edit',['gradeId'=>$gb],$duplicate);$cases[]=$case;$expect($case,500,'等级权重已存在');$check($beforeReject===$query('SELECT * FROM yoshop_user_grade WHERE store_id=? ORDER BY grade_id',[$tenant]),'Duplicate edit changed rows');
    $edited=$gradeA;$edited['name']=$baseName.'_修改\'中文';$edited['weight']=3;$edited['upgrade']=['expend_money'=>'123.45'];$edited['equity']=['discount'=>9.25];
    $case=$request($module,'grade','EDIT_JSON','edit',['gradeId'=>$ga],$edited);$cases[]=$case;$expect($case,200,'更新成功');$ownRow($module,'grade',$edited['name'],$edited+['is_delete'=>0],$ga);
    $read($module,'grade','ALL_ORDER_CHANGED','all',[],2);$read($module,'grade','LIST_DISABLED','list',['status'=>0],1);$read($module,'grade','PAGE_TWO','list',['page'=>2],0);
    foreach([['A',$ga,$edited],['B',$gb,$gradeB]] as $item){$case=$request($module,'grade','DELETE_'.$item[0],'delete',['gradeId'=>$item[1]]);$cases[]=$case;$expect($case,200,'删除成功');$ownRow($module,'grade',$item[2]['name'],$item[2]+['is_delete'=>1],$item[1]);}
    $read($module,'grade','ALL_AFTER_DELETE','all',[],0);
    if(($argv[1]??'')==='run-grade')continue;
    $planA=['plan_name'=>$baseName.'_套餐\'甲','money'=>'100.55','gift_money'=>'10.25','sort'=>20];$planB=['plan_name'=>$baseName.'_套餐乙','money'=>5.5,'gift_money'=>0,'sort'=>10];
    foreach([['A',$planA],['B',$planB]] as $item){$case=$request($module,'plan','ADD_'.$item[0],'add',[],$item[1]);$cases[]=$case;$expect($case,200,'添加成功');$desired=$item[1];$desired['money']=$item[0]==='A'?'100.55':'5.50';$desired['gift_money']=$item[0]==='A'?'10.25':'0.00';$r=$ownRow($module,'plan',$item[1]['plan_name'],$desired+['is_delete'=>0]);if($item[0]==='A')$pa=(int)$r['plan_id'];else $pb=(int)$r['plan_id'];}
    $read($module,'plan','LIST_NONEMPTY','list',[],2);$read($module,'plan','SEARCH_QUOTE','list',['search'=>"套餐'甲"],1);
    $planEdited=['plan_name'=>$baseName.'_修改套餐','money'=>'13.675','gift_money'=>'1.235','sort'=>10];$case=$request($module,'plan','EDIT_DECIMAL_ROUND','edit',['planId'=>$pa],$planEdited);$cases[]=$case;$expect($case,200,'更新成功');
    $desired=$planEdited;$desired['money']='13.68';$desired['gift_money']='1.24';$ownRow($module,'plan',$planEdited['plan_name'],$desired+['is_delete'=>0],$pa);
    $read($module,'plan','LIST_SORT_TIE_PK','list',[],2);$read($module,'plan','PAGE_TWO','list',['page'=>2],0);
    foreach([['A',$pa,$desired],['B',$pb,['plan_name'=>$planB['plan_name'],'money'=>'5.50','gift_money'=>'0.00','sort'=>10]]] as $item){$case=$request($module,'plan','DELETE_'.$item[0],'delete',['planId'=>$item[1]]);$cases[]=$case;$expect($case,200,'删除成功');$ownRow($module,'plan',$item[2]['plan_name'],$item[2]+['is_delete'=>1],$item[1]);}
    $read($module,'plan','LIST_AFTER_DELETE','list',[],0);
}
}catch(Throwable $caught){$error=['class'=>get_class($caught),'message'=>$caught->getMessage(),'file'=>basename($caught->getFile()),'line'=>$caught->getLine()];}
// Include any partial owned HTTP insert even if a later response/projection failed.
$clean=$connect();$cleanupDeletes=[];foreach($tables as $table=>$pk){$nameColumn=$table==='user_grade'?'name':'plan_name';foreach($tenants as $module=>$tenant){
    $rows=$query('SELECT '.$pk.','.$nameColumn.' FROM yoshop_'.$table.' WHERE store_id=? AND '.$nameColumn.' LIKE ?',[$tenant,$marker.'_'.$module.'%']);
    foreach($rows as $row){$id=(int)$row[$pk];$check($id>0&&strpos($row[$nameColumn],$marker.'_'.$module)===0,'Cleanup own original PK/marker');
        $s=$clean->prepare('DELETE FROM yoshop_'.$table.' WHERE '.$pk.'=? AND store_id=? AND '.$nameColumn.'=?');$s->execute([$id,$tenant,$row[$nameColumn]]);$cleanupDeletes[]=['table'=>$table,'own_pk_store_exact_name'=>true];}}}
foreach($tokens as $token)$cache->delete($token);$fresh=$connect();$remaining=[];foreach($tables as $table=>$pk)$remaining[$table]=(int)$fresh->query('SELECT COUNT(*) FROM yoshop_'.$table.' WHERE store_id IN (2147000721,2147000722)')->fetchColumn();
$tokensGone=true;foreach($tokens as $token)$tokensGone=$tokensGone&&$cache->get($token)===null;
$refs=[];$referenceExclusions=[];$referenceColumns=$dm?$query("SELECT TABLE_NAME,COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME LIKE 'YOSHOP_%' AND COLUMN_NAME IN ('GRADE_ID','OLD_GRADE_ID','NEW_GRADE_ID','PLAN_ID') ORDER BY TABLE_NAME,COLUMN_NAME"):
    $query("SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE 'yoshop_%' AND COLUMN_NAME IN ('grade_id','old_grade_id','new_grade_id','plan_id') ORDER BY TABLE_NAME,COLUMN_NAME");
foreach($referenceColumns as $col){$table=strtolower($col['table_name']);$key=strtolower($col['column_name']);$check(preg_match('/^yoshop_[a-z0-9_]+$/D',$table)===1,'Reference metadata');$kind=$key==='plan_id'?'plan':'grade';$sum=0;
    if(!$dm&&$table==='yoshop_ai_training_plan'&&$key==='plan_id'){
        // Column-name equality is not a relationship. This independent training
        // plan PK collided numerically once; never delete that natural row.
        $definition=$query("SELECT COLUMN_KEY,COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='yoshop_ai_training_plan' AND COLUMN_NAME='plan_id'");
        $comment=$query("SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='yoshop_ai_training_plan'");
        $fk=(int)$query("SELECT COUNT(*) n FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='yoshop_ai_training_plan' AND COLUMN_NAME='plan_id' AND REFERENCED_TABLE_NAME IS NOT NULL")[0]['n'];
        $check(count($definition)===1&&$definition[0]['column_key']==='PRI'&&$definition[0]['column_comment']==='训练计划ID'&&$comment[0]['table_comment']==='AI跑步训练计划表'&&$fk===0,'Unrelated plan PK semantic evidence changed');
        $referenceExclusions[]=['table'=>$table,'column'=>$key,'column_key'=>'PRI','column_comment'=>'训练计划ID','table_comment'=>'AI跑步训练计划表','foreign_key'=>false,'reason'=>'independent-training-primary-key-not-a-recharge-plan-reference'];continue;
    }
    foreach(array_keys($idMap[$kind]??[]) as $id)$sum+=(int)$query('SELECT COUNT(*) n FROM '.$table.' WHERE '.$key.'=?',[$id])[0]['n'];$refs[]=['table'=>$table,'column'=>$key,'remaining_owned_references'=>$sum];}
$cleanup=['fresh_pdo_owned_rows'=>$remaining,'tokens_absent'=>$tokensGone,'natural_two_tables_full_sha_unchanged'=>$snapshot()===$before,'owned_reference_columns'=>$refs,'nonreference_homonym_columns'=>$referenceExclusions,'owned_references_zero'=>!array_sum(array_column($refs,'remaining_owned_references')),'delete_scopes'=>$cleanupDeletes];
foreach($cases as &$case){$trace=$case['body']['trace_id']??null;$case['trace_format_valid']=is_string($trace)&&preg_match('/^[0-9a-f]{13}$/D',$trace)===1;if($case['trace_format_valid'])$case['body']['trace_id']='VALID_DYNAMIC_TRACE';}unset($case);
$events=[];foreach(file($runtime.'/queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$events[]=json_decode($line,true);$audit=[];foreach(file($runtime.'/write-audit.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$audit[]=json_decode($line,true);
file_put_contents($runtime.'/private-responses.json',json_encode($privateResponses,JSON_UNESCAPED_UNICODE));file_put_contents($runtime.'/private-id-map.json',json_encode($idMap));
$report=['lane'=>$lane,'cases'=>$cases,'original_proofs'=>$proofs,'metadata_gate_verified'=>true,'loaded_boundary'=>$boundary,'natural_before'=>$before,'write_audit'=>$audit,'cleanup'=>$cleanup,'error'=>$error,'events'=>$events];
file_put_contents($runtime.'/report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));
echo json_encode(['lane'=>$lane,'http_cases'=>count($cases),'original_proofs'=>count($proofs),'cleanup'=>$cleanup,'error'=>$error,'report'=>$runtime.'/report.json']),"\n";
exit($error!==null||array_sum($remaining)!==0||!$tokensGone||!$cleanup['natural_two_tables_full_sha_unchanged']||!$cleanup['owned_references_zero']||in_array(false,array_column($audit,'owned_scope_verified'),true)?1:0);
