<?php
const AF_PLAN_B='aaf34ee25a0fc5f04ea94cdd844683eb88e03f58d7fbbdbb5eaab000581ffc96';
const AF_PLAN_C='a6cc1537001971255467fe9ccf98c002e0d3167c151896d0e494981adf67ea44';
function af_need($v,$m){if(!$v)throw new RuntimeException($m);}
function af_json($v){$j=json_encode($v,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRESERVE_ZERO_FRACTION);af_need($j!==false,'JSON encoding');return $j;}
function af_sort($v){if(!is_array($v))return $v;foreach($v as &$x)$x=af_sort($x);unset($x);if($v!==[]&&array_keys($v)!==range(0,count($v)-1))ksort($v,SORT_STRING);return $v;}
function af_equal($a,$b,$m){af_need(af_sort($a)===af_sort($b),$m);}
function af_gate($p){
 af_need(in_array($p['lane']??'', ['B','C'],true),'Known side');$f=__DIR__.'/plan-'.$p['lane'].'.json';$sha=$p['lane']==='B'?AF_PLAN_B:AF_PLAN_C;af_need(hash_file('sha256',$f)===$sha,'Exact frozen external read plan SHA');af_equal($p,json_decode(file_get_contents($f),true),'Exact immutable SQL/binds/order/owner/budget');
 af_need($p['ceiling']===100&&count($p['steps'])===$p['readonly_attempts']&&count($p['steps'])<=100,'Unified both-DB side100 ceiling');foreach($p['steps']as $i=>$s){af_need($s['ordinal']===$i+1&&in_array($s['db'],['AI','ZHCT'],true),'Exact known connection/order');af_need(strpos($s['SQL'],'SELECT ')===0||preg_match('/^SHOW CREATE TABLE (?:ydy_equipment|ydy_prepay_order|ydy_meal_order|ydy_config|yoshop_user)$/D',$s['SQL'])===1,'Only frozen read SQL');af_need(strpos($s['SQL'],';')===false&&strpos($s['SQL'],'--')===false&&strpos($s['SQL'],'/*')===false,'No extra statement/comment');af_need(substr_count($s['SQL'],'?')===count($s['binds'])&&count($s['PDO_types'])===count($s['binds']),'Typed arity');foreach($s['binds']as $j=>$v)af_need(is_string($v)&&$s['PDO_types'][$j]===2,'Every exact PHPstring/PDO2 bind');}return count($p['steps']);
}
function af_inputs($p,$dir){$file=$dir.'/'.$p['baseline_file'];af_need(is_file($file)&&!is_link($file)&&(fileperms($file)&0777)===0600&&(fileperms(dirname($file))&0777)===0700,'Private original baseline600/700');af_need(hash_file('sha256',$file)===$p['baseline_sha256'],'Frozen private baseline SHA');$b=json_decode(file_get_contents($file),true);af_need($b['lane']===$p['lane']&&count($b['old13'])===13&&count($b['own16'])===16,'Exact source13/own16 scope');af_equal($b['ids'],$p['ids'],'Real AI/Store/Staff/R IDs');af_need(hash_file('sha256',$dir.'/'.$p['signature_source_file'])===$p['signature_source_sha256'],'Private reviewed original signature code SHA');return $b;}
function af_append($h,$event){$j=af_json($event)."\n";af_need(fwrite($h,$j)===strlen($j)&&fflush($h),'Append-before/result durable');}
function af_materialize($rows){$types=[];$keys=[];$streams=[];$bits=[];foreach($rows as $i=>&$r){$keys[$i]=array_keys($r);foreach($r as $k=>&$v){$n=strtolower($k);$types[$i][$n]=gettype($v);if(is_float($v))$bits[$i][$n]=bin2hex(pack('d',$v));if(is_resource($v)){$v=stream_get_contents($v);af_need(is_string($v),'Full original stream');$streams[$i][$n]=['bytes'=>strlen($v),'sha256'=>hash('sha256',$v)];}}unset($v);$r=array_change_key_case($r,CASE_LOWER);}unset($r);return ['rows'=>$rows,'native_types'=>$types,'native_keys'=>$keys,'native_streams'=>$streams,'native_float_bits'=>$bits];}
function af_pdos($p){$db=[];foreach(['AI','ZHCT']as $name){$schema=getenv('POC_'.$name.'_SCHEMA');af_need($schema===$p['expected_schemas'][$name],'Exact approved DB/schema environment');if($p['lane']==='C'){af_need(getenv('POC_'.$name.'_HOST')==='127.0.0.1'&&getenv('POC_'.$name.'_PORT')===($name==='AI'?'5238':'5237'),'Exact approved DM loopback instance');$dsn='dm:host=127.0.0.1;port='.getenv('POC_'.$name.'_PORT').';schema='.$schema;}else{$dsn='mysql:host='.getenv('POC_'.$name.'_HOST').';port='.getenv('POC_'.$name.'_PORT').';dbname='.$schema.';charset='.getenv('POC_'.$name.'_CHARSET');}$db[$name]=new PDO($dsn,getenv('POC_'.$name.'_USER'),getenv('POC_'.$name.'_PASSWORD'),[PDO::ATTR_CASE=>PDO::CASE_LOWER,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_ORACLE_NULLS=>PDO::NULL_NATURAL,PDO::ATTR_STRINGIFY_FETCHES=>false,PDO::ATTR_EMULATE_PREPARES=>false]);}return $db;}
function af_schema($p,$records,$b){
 $c=$p['lane']==='C';$by=[];foreach($records['schema.columns']['rows']as $r)$by[strtoupper($r['table_name'])][]=$r;$summary=[];$prepared=['CODE','NAME','TYPE','PRINT_RECEIPT','SN','RESTAURANT_ID','STATUS','DEL_FLAG','MATE_FLAG','HEARTTIME','CREATE_TIME','CREATE_BY','UPDATE_TIME','UPDATE_BY'];
 foreach(['EQUIPMENT','PREPAY_ORDER','MEAL_ORDER','CONFIG']as $short){$name='YDY_'.$short;af_need(isset($by[$name])&&count($by[$name])>0,'Actual current complete table metadata '.$name);$ddl=$records['schema.ddl.'.($c?$name:strtolower($name))]['rows'][0];$text=$c?$ddl['ddl']:$ddl['create table'];$identity=preg_match('/\bIDENTITY\s*\(/i',$text)===1||stripos($text,'AUTO_INCREMENT')!==false;$required=[];foreach($by[$name]as $col){$n=strtoupper($col['column_name']);$nullable=$c?$col['nullable']==='Y':$col['is_nullable']==='YES';$default=$col[$c?'data_default':'column_default'];if($short==='EQUIPMENT'&&!in_array($n,$prepared,true)&&!($n==='ID'&&$identity)&&!$nullable&&$default===null)$required[]=$n;}
  $summary[$short]=['columns'=>count($by[$name]),'identity_declared'=>$identity,'DDL_sha256'=>hash('sha256',$text),'unknown_omitted_required'=>$required];
 }
 $bank=array_values(array_filter($by['YDY_MEAL_ORDER'],static function($r){return strtoupper($r['column_name'])==='BANK_USER_NAME';}));af_need(count($bank)===1,'Actual current bank_user_name column');$summary['MEAL_ORDER']['bank_user_name']=$bank[0];
 $dates=['EXPIRE_TIME','PAY_TIME','SHIP_TIME','CREATE_TIME','UPDATE_TIME'];foreach($by['YDY_PREPAY_ORDER']as $r)if(in_array(strtoupper($r['column_name']),$dates,true))af_need((int)$r[$c?'data_scale':'datetime_precision']===0,'Current Prepay sourcefsp0, not obsoletefsp6');
 if($c){$base=$b['formal_prepay_metadata'];$keep=['table_name','column_name','data_type','data_type_mod','data_type_owner','data_length','data_precision','data_scale','nullable','column_id','default_length','data_default','character_set_name','char_col_decl_length','char_length','char_used'];$project=static function($rows)use($keep){return array_map(static function($r)use($keep){return array_intersect_key($r,array_flip($keep));},$rows);};af_equal($project($by['YDY_PREPAY_ORDER']),$project($base['columns']),'Root post-repair Prepay complete declared definitions');
  foreach(['indexes','index_columns','constraints','constraint_columns']as $k){$rows=array_values(array_filter($records['schema.'.$k]['rows'],static function($r){return $r['table_name']==='YDY_PREPAY_ORDER';}));af_equal($rows,$base[$k],'Root post-repair6index/PK unchanged '.$k);}
  af_equal($records['schema.triggers']['rows'],$base['triggers'],'Only accepted new Prepay trigger on four write surfaces');af_need(count($records['schema.FK']['rows'])===0,'Visible write-table incoming/outgoingFK0');af_equal($records['schema.ddl.YDY_PREPAY_ORDER']['rows'][0]['ddl'],$base['table_ddl'][0]['ddl'],'Root current Prepay fullGETDDL');foreach($records['schema.indexes']['rows']as $r)af_need($r['table_owner']==='ZHCT_APP'&&$r['status']==='VALID','Actual same-owner VALID indexes');
 }else af_need($records['schema.triggers']['rows']===[],'No unknown Store write-table source triggers');
 return $summary;
}
function af_money_text($v){$s=(string)$v;af_need(preg_match('/^-?[0-9]+(?:\.[0-9]+)?$/D',$s)===1,'Exact decimal text, no epsilon');if(strpos($s,'.')!==false)$s=rtrim(rtrim($s,'0'),'.');return $s;}
function af_money_extra($s,$rows,$b){if($s['label']==='money.Staff'){$expected=$b['own16']['STORE_S1'][0];af_need(count($rows)===1&&(string)$rows[0]['id']===(string)$expected['id'],'Exact own Staff CAST row');af_need(af_money_text($rows[0]['money_text'])===af_money_text($expected['money']),'Exact unchanged Staff server money text');}else{af_need(count($rows)===2,'Both own StoreUser CAST rows');foreach($rows as $i=>$row){$e=$b['own16'][$i===0?'STORE_U1':'STORE_U2'][0];af_need((string)$row['id']===(string)$e['id']&&af_money_text($row['balance_text'])===af_money_text($e['balance'])&&af_money_text($row['pay_text'])===af_money_text($e['pay_money']),'Own StoreUser exact unchanged server money text');}}}
function af_prepay_rows($p,$rows,$b){$cols=array_map('strtolower',array_column($p['lane']==='C'?$b['formal_prepay_metadata']['columns']:array_map(static function($k){return ['column_name'=>$k];},array_keys($b['own16']['STORE_P1'][0])),'column_name'));$expected=$p['lane']==='C'?$b['formal_prepay_metadata']['rows_before']:$b['own16']['STORE_P1'];af_need(count($rows)===count($expected),'Only approved old Prepay row set');foreach($rows as $i=>$row){foreach($cols as $k)af_equal($row[$k],$expected[$i][$k],'Old physical Prepay full27 native value '.$k);foreach(['expire_time','pay_time','ship_time','create_time','update_time']as $k)af_need($row['f_'.$k]===$row[$k].'.000000','All old Prepay actual whole-secondFF6, no old6hash');foreach(['raw_request_a','raw_request_c','raw_request_b','qrcode_payload']as $k)af_need($row['n_'.$k]===($row[$k]===null?'1':'0'),'All4 complete LOB NULL/empty distinction');}}
function af_private_signature($p,$records,$inputDir){
 $rows=$records['coffee.active']['rows'];af_need(count($rows)<=1,'At most one original active appkey row');af_equal($rows,$records['coffee.active_after']['rows'],'Actual appkey record read-window stable');$source=file_get_contents($inputDir.'/'.$p['signature_source_file']);af_need(preg_match('~private\s+\$defaultAppKey\s*=\s*\x27([^\x27]*)\x27~',$source,$match)===1,'Exact reviewed original fallback source');$value=$rows===[]?'':trim((string)$rows[0]['config_value']);$key=$value!==''?$value:$match[1];$timestamp=(string)time();$rand=bin2hex(random_bytes(16));$sorted=[$key,$timestamp,$rand];sort($sorted,SORT_STRING);$fields=['orderid'=>'CDXPPFORM11_A1','machid'=>'CDXPPFORM11_M1','timestamp'=>$timestamp,'randstr'=>$rand,'sign'=>sha1(implode('',$sorted)),'trackno'=>'P1','name'=>'CDXPPFORM11_COFFEE','price'=>'1','channelid'=>'POC'];return ['scope'=>'Private pure signature freeze only; no Auth/cache/request','source_sha256'=>$p['signature_source_sha256'],'active_config_rows'=>count($rows),'effective_appkey'=>$key,'fallback_used'=>$value==='','ordered_fields'=>$fields,'form_body'=>http_build_query($fields,'','&',PHP_QUERY_RFC1738),'second_HTTP'=>['path'=>'/api/PrepayOrder/scan','body'=>['type'=>'200','torderid'=>'RESOLVE_FROM_ACTUAL_NEW_CODE_RESPONSE_STRING'],'Access-Token'=>'UNRESOLVED_NEW_ROOT_AUTH_LEASE_NO_AUTH_CREATED'],'final_HTTP_source_pin'=>'WAIT_ROOT_NEW_PIN_NOT_OLD6168'];
}
function af_read($p,$inputDir){
 af_gate($p);$planSHA=$p['lane']==='B'?AF_PLAN_B:AF_PLAN_C;af_need(getenv('AF_READ_PLAN_SHA')===$planSHA&&getenv('AF_READ_RUNNER_SHA')===hash_file('sha256',__FILE__),'Frozen approved read-plan/runner SHA before PDO');$b=af_inputs($p,$inputDir);$dir=$p['runtime'];af_need(!file_exists($dir)&&!is_link($dir)&&mkdir($dir,0700),'Once readonly runtime, no repeat');$h=fopen($dir.'/events.jsonl','x');af_need(is_resource($h)&&chmod($dir.'/events.jsonl',0600),'Private original journal');$count=0;$stmt=null;$records=[];$schema=null;$before=[];
 try{af_append($h,['event'=>'start','lane'=>$p['lane'],'plan_sha256'=>$planSHA,'runner_sha256'=>hash_file('sha256',__FILE__),'budget'=>$p['readonly_attempts'],'side_ceiling'=>100,'baseline_sha256'=>$p['baseline_sha256'],'PHP_clock'=>['unix'=>time(),'timezone'=>date_default_timezone_get(),'local'=>date('c'),'UTC'=>gmdate('c')]]);$db=af_pdos($p);
 foreach($p['steps']as $s){af_need($count<$p['readonly_attempts']&&$s===$p['steps'][$count],'Exact next readonly statement/budget');af_append($h,['event'=>'append-before','statement'=>$s]);$count++;$stmt=$db[$s['db']]->prepare($s['SQL']);foreach($s['binds']as $i=>$v)af_need($stmt->bindValue($i+1,$v,2),'Exact native string/PDO2 binding');af_need($stmt->execute(),'Original readonly execute');$rec=af_materialize($stmt->fetchAll(PDO::FETCH_ASSOC));af_append($h,array_merge(['event'=>'original-read-result','statement'=>$s,'errorInfo'=>$stmt->errorInfo(),'rowCount'=>$stmt->rowCount()],$rec));af_need(count($rec['rows'])<=2000,'Bounded scope/catalog result');$records[$s['label']]=$rec;$rows=$rec['rows'];
  if($s['role']==='identity'){af_need(count($rows)===1&&$rows[0]['schema_name']===$p['expected_schemas'][$s['db']],'Actual exact two-client identity');if($p['lane']==='B')af_need($rows[0]['version']==='5.6.50-log'&&$rows[0]['sql_mode']==='NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION','Actual approved code-local MySQL56 mode');}
  elseif($s['role']==='version')af_need(in_array('03134284488-20260212-314192-20200',array_column($rows,'banner'),true),'Actual approved DM build');
  elseif($s['label']==='auth.userDDL'){$schema=af_schema($p,$records,$b);af_append($h,['event'=>'actual-schema-gate','summary'=>$schema]);}
  elseif($s['role']==='protection'){$expected=$b[$s['surface']][$s['key']];if($s['key']==='ZHCT.exact_config_hashes')af_need(hash('sha256',af_json(af_sort($rows)))===$expected,'Original config full-row SHA unchanged');else af_equal($rows,$expected,'Protected actual original rows '.$s['surface'].'.'.$s['key']);if($s['phase']==='before')$before[$s['surface'].'.'.$s['key']]=$rec;else{af_equal($rows,$before[$s['surface'].'.'.$s['key']]['rows'],'Fresh before/after full rows');af_equal($rec['native_types'],$before[$s['surface'].'.'.$s['key']]['native_types'],'Fresh before/after native types');af_equal($rec['native_float_bits'],$before[$s['surface'].'.'.$s['key']]['native_float_bits'],'Fresh before/after float bits');}}
  elseif($s['role']==='collision')af_need($rows===[],'Exact new marker collision0 '.$s['label']);
  elseif($s['role']==='private_key')af_need(count($rows)<=1,'Original active coffee key <=1; no arbitrary last row');
  elseif($s['role']==='money')af_equal($rows,$b['money'][$s['key']]['rows'],'All original declared DECIMAL serverCAST unchanged '.$s['key']);
  elseif($s['role']==='money_extra')af_money_extra($s,$rows,$b);
  elseif($s['label']==='auth.store')af_need(count($rows)===1&&!(bool)$rows[0]['is_delete']&&!(bool)$rows[0]['is_recycle'],'Original live store controller legal identity, no Auth call');
  elseif($s['role']==='prepay_definition')af_equal($rows[0]['ddl'],$b['formal_prepay_metadata']['triggers'][0]['trigger_body'],'Current formal complete trigger native definition');
  elseif($s['role']==='prepay_rows')af_prepay_rows($p,$rows,$b);
 }
 af_need($count===$p['readonly_attempts']&&count($before)===29,'Complete exact unified readonly ledger/29before/29after');$private=af_private_signature($p,$records,$inputDir);$file=$dir.'/private-request-plan.json';$out=fopen($file,'x');af_need(is_resource($out)&&chmod($file,0600),'Private signature request file');fwrite($out,af_json($private)."\n");fclose($out);af_append($h,['event'=>'complete','lane'=>$p['lane'],'readonly_attempts'=>$count,'schema_summary'=>$schema,'old13_own16_and_001_002_unchanged'=>true,'private_request_sha256'=>hash_file('sha256',$file),'actual_signature_config_rows'=>count($records['coffee.active']['rows']),'Auth'=>0,'HTTP'=>0,'DML'=>0,'DDL'=>0,'final_source_full_pin'=>'WAIT_ROOT_NEW_PIN']);fclose($h);echo af_json(['status'=>'ACTUAL_FRESH_READONLY_PASS','lane'=>$p['lane'],'read'=>$count,'Auth'=>0,'HTTP'=>0,'DML'=>0,'DDL'=>0,'raw_sha256'=>hash_file('sha256',$dir.'/events.jsonl'),'private_request_sha256'=>hash_file('sha256',$file)])."\n";return 0;
 }catch(Throwable $e){af_append($h,['event'=>'stopped_readonly_no_retry_no_mutation','class'=>get_class($e),'message'=>$e->getMessage(),'errorInfo'=>$stmt?$stmt->errorInfo():null,'read_attempts'=>$count]);fclose($h);fwrite(STDERR,af_json(['status'=>'STOPPED_READONLY_ORIGINAL_RETAINED','lane'=>$p['lane'],'read'=>$count,'Auth'=>0,'HTTP'=>0,'DML'=>0,'DDL'=>0])."\n");return 1;}
}
if(realpath($_SERVER['SCRIPT_FILENAME'])===__FILE__){try{$lane=$argv[1]??'';af_need(in_array($lane,['B','C'],true),'Known side');$p=json_decode(file_get_contents(__DIR__.'/plan-'.$lane.'.json'),true);$count=af_gate($p);$mode=$argv[2]??'--gate';$dir=$argv[3]??(__DIR__.'/private');if($mode==='--gate'){if(is_dir($dir))af_inputs($p,$dir);echo af_json(['status'=>'PURE_READONLY_GATE_GREEN','lane'=>$lane,'read'=>$count,'side_ceiling'=>100,'runner_sha256'=>hash_file('sha256',__FILE__),'DB'=>0,'SQL'=>0])."\n";exit(0);}af_need($mode==='--read','Only gate or authorized readonly mode');exit(af_read($p,$dir));}catch(Throwable $e){fwrite(STDERR,$e->getMessage()."\n");exit(1);}}
