<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');
$lane=(string)getenv('CATEGORY_LANE');$base=(string)getenv('CATEGORY_BASE');$runtime=(string)getenv('CATEGORY_RUNTIME');
if(!in_array($lane,['B','C'],true)||!preg_match('#^http://127\.0\.0\.1:1832[12]$#D',$base))exit(78);
$dm=getenv('POC_DIALECT')==='dm';
$pdo=new PDO($dm?'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP':'mysql:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';dbname='.getenv('POC_AI_SCHEMA').';charset=utf8',
    getenv('POC_AI_USER'),getenv('POC_AI_PASSWORD'),[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);
$check=static function(bool $ok,string $reason):void{if(!$ok)throw new RuntimeException($reason);};
if($dm)$check($pdo->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()==='AIZHCT_APP','DM identity');
else $check(strpos((string)$pdo->query('SELECT VERSION()')->fetchColumn(),'5.6.')===0&&$pdo->query('SELECT DATABASE()')->fetchColumn()===getenv('POC_AI_SCHEMA'),'Code local MySQL identity');
$check((int)$pdo->query('SELECT COUNT(*) FROM yoshop_store WHERE store_id=10001')->fetchColumn()===1,'Existing legal store context');
$query=static function(string $sql)use($pdo):array{return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);};
$snapshot=static function()use($query):array{$out=[];foreach(['nation','sport'] as $table){$rows=$query('SELECT * FROM yoshop_'.$table.' ORDER BY id');$out[$table]=['rows'=>count($rows),'sha'=>hash('sha256',serialize($rows))];}return $out;};
$before=$snapshot();$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);
$check(!empty($boundary['external_stubs'])&&!empty($boundary['private_cache'])&&$boundary['allowlist']==='store-dictionary-post-only','Private read boundary');
$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]);$token=bin2hex(random_bytes(24));
$cache->set($token,['user'=>['store_user_id'=>2147000621,'store_id'=>10001,'real_name'=>'CDXDICT10READ','user_name'=>'CDXDICT10READ','is_super'=>1],'store_id'=>10001,'is_login'=>true],600);
$convert=static function(array $row):array{foreach($row as $key=>&$value){if(in_array($key,['id','pid','sort','status','is_delete'],true))$value=(int)$value;
    elseif(in_array($key,['create_time','update_time'],true)&&is_numeric($value))$value=date('Y-m-d H:i:s',(int)$value);}unset($value);return $row;};
$tree=static function(array $rows,int $pid=0)use(&$tree):array{$out=[];foreach($rows as $row)if((int)$row['pid']===$pid){$children=$tree($rows,(int)$row['id']);if($children)$row['children']=$children;$out[]=$row;}return $out;};
$shape=static function($v)use(&$shape){if(is_object($v)){$out=[];foreach(get_object_vars($v) as $k=>$x)$out[$k]=$shape($x);ksort($out);return ['object'=>$out];}
    if(is_array($v))return ['array'=>array_map($shape,$v)];if(is_int($v)||is_float($v))return 'number';return gettype($v);};
$canonical=static function($v)use(&$canonical){if(is_object($v)){$vars=get_object_vars($v);ksort($vars);$out=new stdClass;foreach($vars as $k=>$x)$out->$k=$canonical($x);return $out;}
    if(is_array($v))return array_map($canonical,$v);return $v;};
$hash=static function($v)use($canonical):string{return hash('sha256',json_encode($canonical($v),JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));};
$deep=static function($actual,$expected,string $path='$')use(&$deep,$check):void{if(is_array($expected)){$check(is_array($actual),'Source container '.$path);$ak=array_keys($actual);$ek=array_keys($expected);sort($ak);sort($ek);$check($ak===$ek,'Source keys '.$path);foreach($expected as $key=>$x)$deep($actual[$key],$x,$path.'.'.$key);}else $check($actual===$expected,'Source value/type/order '.$path);};
$cases=[];$error=null;$privateResponses=[];file_put_contents($runtime.'/queries.jsonl','');
try{
foreach(['NATION'=>'nation','SPORT'=>'sport'] as $label=>$table){
    // No added ORDER BY in this independent projection: preserve the model's select() order.
    $source=$query('SELECT * FROM yoshop_'.$table);$check(count($source)>0,'Nonempty natural dictionary required');$expected=array_map($convert,$source);if($table==='sport')$expected=$tree($expected);
    $check(count($expected)>0,'Nonempty rendered dictionary required');
    foreach([1,2] as $round){$scenario='PILOT_DICT_'.$label.'_'.$round;$path='/store/store.'.ucfirst($table).'/all';
        $raw=file_get_contents($base.$path,false,stream_context_create(['http'=>['method'=>'POST','header'=>"Access-Token: ".$token."\r\nPlatform: PC\r\nX-Pilot-Scenario: ".$scenario."\r\nContent-Type: application/json\r\n",'content'=>'{}','ignore_errors'=>true,'timeout'=>20]]));
        preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$body=json_decode((string)$raw,true);$object=json_decode((string)$raw);
        $check((int)($m[1]??0)===200&&is_array($body)&&($body['status']??null)===200,'Dictionary HTTP success '.$scenario);
        $trace=$body['trace_id']??null;$check(is_string($trace)&&preg_match('/^[0-9a-f]{13}$/D',$trace)===1,'Trace contract');
        $deep($body['data'],['list'=>$expected]);$wireShape=$shape($object);unset($object->trace_id);
        $privateResponses[$scenario]=['http_status'=>200,'body'=>json_decode((string)$raw)];
        $cases[]=['scenario'=>$scenario,'method'=>'POST','path'=>$path,'round'=>$round,'http_status'=>200,'business_status'=>200,
            'trace_format_valid'=>true,'source_rows'=>count($source),'returned_root_rows'=>count($expected),
            'full_source_projection_equal'=>true,'complete_wire_sha_without_trace'=>$hash((object)['http_status'=>200,'body'=>$object]),
            'raw_json_shape'=>$wireShape,'dictionary_dml'=>0];
    }
}
}catch(Throwable $caught){$error=['class'=>get_class($caught),'message'=>$caught->getMessage(),'line'=>$caught->getLine()];}
$cache->delete($token);$after=$snapshot();$events=[];foreach(file($runtime.'/queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$events[]=json_decode($line,true);
$writes=array_values(array_filter($events,static function(array $e):bool{return ($e['kind']??'')==='query_observed'&&in_array($e['operation'],['insert','update','delete','replace','merge'],true);}));
$cleanup=['token_absent'=>$cache->get($token)===null,'full_natural_tables_unchanged'=>$before===$after,'framework_dml_observed'=>count($writes),'fixture_db_rows_created'=>0];
$report=['lane'=>$lane,'boundary_verified'=>true,'existing_store_context'=>10001,'case_count'=>count($cases),'cases'=>$cases,'cleanup'=>$cleanup,
    'source_before'=>$before,'error'=>$error,'events'=>$events,'normalization'=>'Only validated top-level trace removed from private full-wire hash; no IDs/times/array order normalized'];
file_put_contents($runtime.'/report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));chmod($runtime.'/report.json',0600);
file_put_contents($runtime.'/private-responses.json',json_encode($privateResponses,JSON_UNESCAPED_UNICODE));chmod($runtime.'/private-responses.json',0600);
echo json_encode(['lane'=>$lane,'cases'=>count($cases),'cleanup'=>$cleanup,'error'=>$error,'report'=>$runtime.'/report.json']),"\n";
exit($error!==null||!$cleanup['token_absent']||!$cleanup['full_natural_tables_unchanged']||count($writes)>0?1:0);
