<?php
declare(strict_types=1);
ini_set('display_errors','0');ini_set('log_errors','0');error_reporting(E_ALL);
set_error_handler(function($severity){throw new ErrorException('Sanitized warning',0,$severity);});
$stage='start';$n=0;$executions=[];
function gSame($expected,$actual): void {global $n;++$n;if($expected!==$actual){throw new RuntimeException('Gift read assertion '.$n);}}
function gDigest($data): string {return hash('sha256',json_encode($data,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));}
function offlineFields(): array {throw new RuntimeException('Unexpected virtual schema request');}
try{
    $assets=realpath((string)getenv('S2_ASSETS'));$runtime=realpath((string)getenv('S2_RUNTIME'));$appRoot=realpath((string)getenv('S2_APP_ROOT'));
    if(!$assets||!$runtime||strpos($assets,'/tmp/cdx-gift-read-')!==0||strpos($runtime,$assets.'/')!==0||$appRoot!=='/tmp/cdx-coupon-http-C-9LLQWL/app-root'){throw new RuntimeException('New private path gate');}
    $required=[
        'ProbeRuntime.php'=>'d7a1e080d9fe5a5aac4b4ec45b2987333cfef82eab2601c891c79a94d359ad46',
        'ShadowCore.php'=>'7e727506b543770c6484763536dc8b2f1efac7665015a6368b3c36dc37a55631',
        'ShadowStatement.php'=>'cc3ae6a653e3ae6f85cc4a51799ff2b8fe1b8e1efb394706251b1097c68f82d8',
        'candidate.json'=>'76b0a0e63a0477d4a33f9a2e50692128d51f1fd2757c0861287e138863aa9722',
        'metadata_B.json'=>'70f76ac2218805a7f4fe7a7aa2f8ee220b27e7bcb7dc921b03d91ecfb4b474d3',
        'metadata_C_run.json'=>'71b67951ac2de95c2112523683e86fc3d042cd176c603cab7ced25c59a87c7ee',
    ];
    foreach($required as $file=>$sha){if(hash_file('sha256',$assets.'/'.$file)!==$sha){throw new RuntimeException('Reviewed asset drift');}}
    require $assets.'/ProbeRuntime.php';require $assets.'/ShadowStatement.php';$identity=runtimeIdentity();
    $candidate=json_decode(file_get_contents($assets.'/candidate.json'),true);$sql=$candidate['projected']['sql'];
    if(hash('sha256',$sql)!=='04e5f5a264c39e257cc8ebed4ea1c52cc3d807112ad7e21a3f69de8a18880fa5'||$candidate['max_rows']!==5){throw new RuntimeException('Frozen SQL gate');}
    $spec=$candidate['statement_plan'];$plan=new ShadowPlan($spec);$registry=new ShadowRegistry([$plan]);
    $native=probePdo();$shadow=probePdo(ShadowStatement::class,[$registry,PDO::FETCH_ASSOC]);$identity['mapped']=mappedComponents($identity);$target=independentTarget($shadow);$attributes=effectiveAttributes($shadow);
    $stage='metadata_recheck';$metaSql="SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_PRECISION,DATA_SCALE,CHAR_LENGTH,NULLABLE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YOSHOP_USER_GIFT_LOG' ORDER BY COLUMN_ID";
    $executions[]=hash('sha256',$metaSql);$meta=$native->query($metaSql)->fetchAll(PDO::FETCH_ASSOC);$normalized=[];
    foreach($meta as $r){$normalized[]=['ordinal'=>(int)$r['column_id'],'name'=>strtolower($r['column_name']),'native_type'=>strtolower($r['data_type']),'precision'=>$r['data_precision']===null?null:(int)$r['data_precision'],'scale'=>$r['data_scale']===null?null:(int)$r['data_scale'],'char_length'=>$r['char_length']===null?null:(int)$r['char_length'],'nullable'=>$r['nullable']];}
    $frozen=json_decode(file_get_contents($assets.'/metadata_C_run.json'),true)['report']['columns'];
    // Metadata objects have unordered keys; preserve column list order and all value types.
    foreach($normalized as &$column){ksort($column);}unset($column);foreach($frozen as &$column){ksort($column);}unset($column);gSame($frozen,$normalized);
    $query=static function(PDO $pdo,string $sql)use(&$executions):PDOStatement{$s=$pdo->prepare($sql);$s->bindValue(':owned_min',0,PDO::PARAM_INT);gSame(true,$s->execute());$executions[]=hash('sha256',$sql);return $s;};
    $stage='native_shadow_provenance';$raw=$query($native,$sql)->fetchAll(PDO::FETCH_NUM);gSame(true,count($raw)<=5);gSame(true,count($raw)>0);
    $truthSql='SELECT CAST(g.log_id AS VARCHAR(128)) AS LOG_ID_TEXT,CAST(g.biz_order_id AS VARCHAR(128)) AS BIZ_ORDER_ID_TEXT,CAST(g.money AS VARCHAR(128)) AS MONEY_TEXT,CAST(g.balance_before AS VARCHAR(128)) AS BALANCE_BEFORE_TEXT,CAST(g.balance_after AS VARCHAR(128)) AS BALANCE_AFTER_TEXT FROM yoshop_user_gift_log g WHERE  g.log_id >= :owned_min ORDER BY g.log_id DESC LIMIT 5';
    $truth=$query($native,$truthSql)->fetchAll(PDO::FETCH_NUM);gSame(count($raw),count($truth));$expected=[];$nativeDifferences=0;$wideSamples=0;
    foreach($truth as $i=>$texts){
        $row=[];$row['log_id']=ShadowValue::restore($texts[0],['kind'=>'signed_integer']);$row['biz_order_id']=ShadowValue::restore($texts[1],['kind'=>'unsigned_integer']);
        foreach(['money','balance_before','balance_after'] as $offset=>$name){$row[$name]=ShadowValue::restore($texts[$offset+2],['kind'=>'decimal_text','precision'=>10,'scale'=>2]);}
        gSame(array_values($row),$plan->restore($raw[$i]));$expected[]=$row;
        foreach([1=>1,2=>2,3=>3,4=>4] as $orig=>$text){$rule=$orig===1?['kind'=>'unsigned_integer']:['kind'=>'decimal_text','precision'=>10,'scale'=>2];gSame(ShadowValue::restore($texts[$text],$rule),ShadowValue::restore($raw[$i][$orig+4],$rule));if($raw[$i][$orig]!==array_values($row)[$orig]){++$nativeDifferences;}}
        $digits=ltrim($texts[1]??'','0');if(strlen($digits)>16||(strlen($digits)===16&&strcmp($digits,'9007199254740992')>0)){++$wideSamples;}
    }
    $stage='fetch_all';$stmt=$query($shadow,$sql);gSame(5,$stmt->columnCount());$all=$stmt->fetchAll(PDO::FETCH_ASSOC);gSame($expected,$all);gSame([],$stmt->fetchAll(PDO::FETCH_ASSOC));
    $stage='fetch_rows';$stmt=$query($shadow,$sql);foreach($expected as $row){gSame($row,$stmt->fetch(PDO::FETCH_ASSOC));}gSame(false,$stmt->fetch(PDO::FETCH_ASSOC));
    $stage='fetch_columns';for($column=0;$column<5;++$column){$stmt=$query($shadow,$sql);foreach($expected as $row){gSame(array_values($row)[$column],$stmt->fetchColumn($column));}gSame(false,$stmt->fetchColumn($column));}
    foreach($all as $row){gSame(['log_id','biz_order_id','money','balance_before','balance_after'],array_keys($row));}
    $stage='actual_model_hydrate';$hydrate=['status'=>'not_supported_php73_typed_business_source','assertions'=>0];$modelWire=[];$sourceIdentity=[];
    if(PHP_MINOR_VERSION===4){
        foreach(['ProjectionContract.php'=>'fc022082a9b29dee6d3a34a2d80123852df341185a8a90399c22c68d458672c0','OfflineAi.php'=>'0556147b4a06035acf451f27b2825212fe6e6e8fe902cad9c5e25c1778ff42aa'] as $file=>$sha){if(hash_file('sha256',$assets.'/'.$file)!==$sha){throw new RuntimeException('Offline hydrate asset drift');}}
        // ProjectionContract requires a sibling original core; it is not used here. OfflineAi definitions only need actual framework classes.
        $loader=require $appRoot.'/vendor/autoload.php';$loader->setPsr4('app\\',$appRoot.'/app/');$loader->setPsr4('cores\\',$appRoot.'/cores/');
        spl_autoload_register(static function(string $class)use($appRoot):void{foreach(['app\\'=>'app/','cores\\'=>'cores/'] as $prefix=>$dir){if(strpos($class,$prefix)===0){$file=$appRoot.'/'.$dir.str_replace('\\','/',substr($class,strlen($prefix))).'.php';if(is_file($file)){require $file;}return;}}},true,true);
        require $assets.'/OfflineAi.php';$connection=new OfflineAiConnection(['database'=>'offline_contract','prefix'=>'yoshop_','debug'=>false]);$manager=new OfflineAiManager($connection);$connection->setDb($manager);
        foreach($normalized as $col){$connection->giftFields[$col['name']]=['name'=>$col['name'],'type'=>$col['native_type'],'notnull'=>$col['nullable']==='N','default'=>null,'primary'=>$col['name']==='log_id','autoinc'=>false];}
        require $appRoot.'/vendor/topthink/framework/src/helper.php';require $appRoot.'/app/common.php';$app=new \think\App($runtime);$app->instance('http',new class{public function getName(){return 'api';}});\think\App::$store_id=1930401010;
        $expectedSources=['app/api/model/user/GiftLog.php'=>'fc28131f940b8c0e925a94158394506998a124329e81b4473932e205c6c26810','cores/BaseModel.php'=>'15b09472d9152906bd3f6cfa6b98ea87b1952abcc9587e7e72526fff73ab78c6','app/common/service/FixedScaleDecimalResponse.php'=>'c3bce04654a313e2918667a8a1019eb7b4c45b98edf9a5218d3e8c40b8d333cc'];
        foreach($expectedSources as $f=>$sha){if(hash_file('sha256',$appRoot.'/'.$f)!==$sha){throw new RuntimeException('Actual model source drift');}$sourceIdentity[$f]=$sha;}
        foreach([\app\api\model\user\GiftLog::class=>'app/api/model/user/GiftLog.php',\cores\BaseModel::class=>'cores/BaseModel.php',\app\common\service\FixedScaleDecimalResponse::class=>'app/common/service/FixedScaleDecimalResponse.php'] as $class=>$file){$actual=(new ReflectionClass($class))->getFileName();gSame(realpath($appRoot.'/'.$file),realpath($actual));gSame($expectedSources[$file],hash_file('sha256',$actual));}
        foreach(['app_name','getStoreId'] as $function){$actual=(new ReflectionFunction($function))->getFileName();gSame(realpath($appRoot.'/app/common.php'),realpath($actual));gSame('c83bd472fdaa75afe201078504ef5a0a010f6d3535b9816c1f0de25b2ef4f66f',hash_file('sha256',$actual));}
        $prototype=new \app\api\model\user\GiftLog();$start=$n;
        foreach($all as $row){$m=$prototype->newInstance($row);gSame($row,$m->getData());gSame($row['biz_order_id'],$m->getAttr('biz_order_id'));gSame($row,$m->toArray());$wire=json_encode($m->toArray(),JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR);gSame($row,json_decode($wire,true));$modelWire[]=$wire;}
        gSame(0,$connection->denied);$hydrate=['status'=>'pass_actual_php74_model','assertions'=>$n-$start,'private_schema_provider_reads'=>$connection->giftSchemaReads,'physical_sources'=>$sourceIdentity];
    }
    $private=['raw_native'=>$raw,'independent_text_truth'=>$truth,'restored'=>$all,'actual_model_wire'=>$modelWire];$file=$runtime.'/php'.PHP_MINOR_VERSION.'-rows-private.json';file_put_contents($file,json_encode($private,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));chmod($file,0600);
    $report=['status'=>'pass','scope'=>'one_existing_table_fixed_five_column_readonly_not_global_compatibility','php'=>PHP_VERSION,'assertions'=>$n,'sample_rows'=>count($all),'wide_above_2pow53_samples'=>$wideSamples,'native_vs_restored_field_type_or_value_differences'=>$nativeDifferences,'raw_native_digest'=>gDigest($raw),'text_truth_digest'=>gDigest($truth),'restored_digest'=>gDigest($all),'private_evidence_sha256'=>hash_file('sha256',$file),'hydrate'=>$hydrate,'identity'=>$identity,'target'=>$target,'attributes'=>$attributes,'metadata_rechecked_equal'=>true,'candidate_sql_sha256'=>hash('sha256',$sql),'truth_sql_sha256'=>hash('sha256',$truthSql),'actual_sql_sha256'=>$executions,'source_hashes'=>$required,'business_dml'=>0,'ddl'=>0,'cache_enabled'=>false,'http_increment'=>0,'global_p0_closed'=>false];
    echo json_encode($report,JSON_UNESCAPED_SLASHES|JSON_PRESERVE_ZERO_FRACTION),PHP_EOL;
}catch(Throwable $e){echo json_encode(['status'=>'fail','stage'=>$stage,'php'=>PHP_VERSION,'assertions'=>$n,'class'=>get_class($e),'code'=>(string)$e->getCode(),'file'=>basename($e->getFile()),'line'=>$e->getLine(),'executed_sql_sha256'=>$executions,'business_dml'=>0,'ddl'=>0]),PHP_EOL;exit(1);}
