<?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:1834[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 context');
$query=static function(string $sql,array $args=[])use($pdo):array{if(!preg_match('/^SELECT\b/i',$sql))throw new RuntimeException('SELECT only');$s=$pdo->prepare($sql);$s->execute($args);$rows=[];while($row=$s->fetch(PDO::FETCH_ASSOC)){$row=array_change_key_case($row,CASE_LOWER);foreach($row as &$v)if(is_resource($v))$v=stream_get_contents($v);unset($v);$rows[]=$row;}return $rows;};
$snapshot=static function()use($query):array{$rows=$query('SELECT * FROM yoshop_config_enum ORDER BY id');return ['rows'=>count($rows),'sha'=>hash('sha256',serialize($rows))];};
$before=$snapshot();$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);$check(!empty($boundary['external_stubs'])&&$boundary['allowlist']==='enum-store-terminal-post-only','Private boundary');
$metadata=$dm?$query("SELECT COLUMN_NAME,DATA_TYPE,DATA_PRECISION AS PRECISION_VALUE,DATA_SCALE AS SCALE_VALUE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YOSHOP_CONFIG_ENUM' ORDER BY COLUMN_ID"):$query("SELECT COLUMN_NAME,COLUMN_TYPE,DATA_TYPE,NUMERIC_PRECISION AS PRECISION_VALUE,NUMERIC_SCALE AS SCALE_VALUE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='yoshop_config_enum' ORDER BY ORDINAL_POSITION");
$types=[];foreach($metadata as $column)$types[strtolower($column['column_name'])]=$column;
// Fixed source-reviewed EnumType whitelist; never interpolate user enumType into SQL fields.
$fields=[1=>'*',2=>'enum_key,enum_name,people,ratio',3=>'enum_key,enum_name',4=>'enum_key,enum_name,recommend',5=>'enum_key,enum_name',6=>'enum_key,enum_type,enum_name,set_value',7=>'enum_key,enum_type,enum_name,set_value',8=>'enum_key,enum_type,enum_name,set_value',9=>'enum_key,enum_type,enum_name,set_value',10=>'enum_key,enum_pid,enum_name,set_value',11=>'enum_key,enum_pid,enum_name',12=>'enum_key,enum_name',13=>'enum_key,enum_name',14=>'enum_key,enum_name',15=>'enum_key,enum_name',16=>'enum_key,enum_name',17=>'enum_key,enum_name'];
$convert=static function(array $row)use($types):array{foreach($row as $key=>&$value){if(in_array($key,['is_delete','create_by','create_time','update_time'],true))continue;
    if($value===null)continue;$type=strtolower($types[$key]['data_type']??'');
    if(preg_match('/^(tinyint|smallint|mediumint|int|integer|bigint|bit)$/',$type))$value=(int)$value;
    elseif($key==='ratio')$value=bcadd((string)$value,'0',2);
    elseif(in_array($type,['float','double','real'],true))$value=(float)$value;
}unset($value);foreach(['is_delete','create_by','create_time','update_time'] as $key)unset($row[$key]);return $row;};
$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)];return is_int($v)||is_float($v)?'number':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;}return is_array($v)?array_map($canonical,$v):$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));};
$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'=>2147000641,'store_id'=>10001,'real_name'=>'CDXENUM10READ','user_name'=>'CDXENUM10READ','is_super'=>1],'store_id'=>10001,'is_login'=>true],600);
$cases=[];$error=null;file_put_contents($runtime.'/queries.jsonl','');
try{
 $getOnly=getenv('CATEGORY_GET_ONLY')==='1';$paths=$getOnly?['/terminal/Enum/all']:['/store/enum/all','/terminal/Enum/all'];
 foreach($paths as $routeIndex=>$path){$method=$getOnly?'GET':'POST';
  $inputs=[];foreach(range(1,17) as $n)$inputs['TYPE_'.$n]=(string)$n;$inputs+=['UNKNOWN'=>'999','EMPTY'=>'','QUOTE'=>"'",'ALPHA'=>'abc','PREFIX'=>'2abc','SPACE'=>' 2 ','POSITIVE'=>'+2','NEGATIVE'=>'-2','DECIMAL'=>'2.1','ZERO_DECIMAL'=>'2.0','EXPONENT'=>'2e0','HUGE_EXPONENT'=>'2e999','OVER_RANGE'=>'256','ZERO'=>'0','NULL'=>null,'DOTTED'=>'.2','SIGN_ONLY'=>'+','SPACE_ONLY'=>'  ','QUOTE_PREFIX'=>"'2",'NEAR_INTEGER_LOW'=>'2.0000000000000001','NEAR_INTEGER_HIGH'=>'2.0000000000000004','NEGATIVE_EXPONENT'=>'2e-999','MAX_TINYINT'=>'255','MIN_NEGATIVE'=>'-128'];
  foreach($inputs as $label=>$input){$scenario='PILOT_DICT_ENUM_'.$routeIndex.'_'.$label;$filtered=is_string($input)?trim($input):$input;
   // Independent oracle: these fixed label outcomes were observed directly on local MySQL before the patch.
   // Do not call the implementation's numeric helper as the source oracle.
   $sourceParam=$filtered;$forceNoMatch=$dm&&in_array($label,['DECIMAL','DOTTED','HUGE_EXPONENT','NEAR_INTEGER_HIGH','NEGATIVE','MIN_NEGATIVE','UNKNOWN','OVER_RANGE'],true);
   if($dm&&in_array($label,['QUOTE','ALPHA','SIGN_ONLY','SPACE_ONLY','QUOTE_PREFIX','EMPTY','ZERO','NEGATIVE_EXPONENT'],true))$sourceParam='0';
   if($dm&&in_array($label,['PREFIX','NEAR_INTEGER_LOW'],true))$sourceParam='2';
   $sql='SELECT '.($fields[$filtered]??'*').' FROM yoshop_config_enum WHERE '.($forceNoMatch?'1=0':'enum_type=?').' AND is_delete=0 ORDER BY '.($dm?'"ORDER"':'`order`').' ASC';$sourceError=null;$source=[];
   try{$source=$query($sql,$forceNoMatch?[]:[$sourceParam]);}catch(Throwable $e){$sourceError=['class'=>get_class($e),'message_sha256'=>hash('sha256',$e->getMessage())];}
   $expected=array_map($convert,$source);
   $request=['method'=>$method,'header'=>"Access-Token: ".$token."\r\nPlatform: PC\r\nX-Pilot-Scenario: ".$scenario."\r\nContent-Type: application/json\r\n",'ignore_errors'=>true,'timeout'=>20];$url=$base.$path;
   if($method==='POST')$request['content']=json_encode(['enumType'=>$input]);else $url.='?'.http_build_query(['enumType'=>$input]);
   $raw=@file_get_contents($url,false,stream_context_create(['http'=>$request]));
   preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$body=json_decode((string)$raw,true);$object=json_decode((string)$raw);$status=(int)($m[1]??0);$case=['scenario'=>$scenario,'path'=>$path,'method'=>$method,'input_label'=>$label,'http_status'=>$status,'business_status'=>$body['status']??null,'source_error'=>$sourceError,'source_rows'=>count($source),'raw_json_shape'=>$shape($object)];
   $check(is_array($body)&&is_object($object),'JSON response '.$scenario);$trace=$body['trace_id']??null;$check(is_string($trace)&&preg_match('/^[0-9a-f]{13}$/D',$trace)===1,'Trace contract');unset($object->trace_id);
   $case['complete_wire_sha_without_trace']=$hash((object)['http_status'=>$status,'body'=>$object]);$case['projected_source_sha']=$hash((object)['list'=>$expected]);$case['trace_format_valid']=true;
   $case['full_source_projection_equal']=$status===200&&($body['status']??null)===200&&($body['data']??null)===['list'=>$expected];
   $case['returned_rows']=is_array($body['data']['list']??null)?count($body['data']['list']):null;
   if(($body['status']??null)!==200)$case['message_sha256']=hash('sha256',(string)($body['message']??$body['msg']??''));
   $case['source_oracle']=$dm?'Fixed label comparison outcomes observed on original local MySQL; projected from actual DM rows':'Original local MySQL comparison';$cases[]=$case;
   if(strpos($label,'TYPE_')===0)$check($case['full_source_projection_equal']&&count($source)>0,'Complete source projection '.$scenario);
  }
 }
}catch(Throwable $e){$error=['class'=>get_class($e),'message'=>$e->getMessage(),'line'=>$e->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_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_table_unchanged'=>$before===$after,'framework_dml_observed'=>count($writes),'fixture_db_rows_created'=>0];$report=['lane'=>$lane,'case_count'=>count($cases),'cases'=>$cases,'cleanup'=>$cleanup,'source_before'=>$before,'schema'=>$metadata,'error'=>$error,'events'=>$events,'normalization'=>'Only validated top-level trace removed; no values, IDs, times or 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);echo json_encode(['lane'=>$lane,'cases'=>count($cases),'cleanup'=>$cleanup,'error'=>$error,'report'=>$runtime.'/report.json']),"\n";exit($error||!$cleanup['token_absent']||!$cleanup['full_natural_table_unchanged']||$writes?1:0);
