<?php
declare(strict_types=1);
const NF_PLAN_SHA='68124d2e3fdafa943e70c8c4a4f0b4e0c4dfb67098bb67df8615d7d1da41fc94';
function nf_check($condition,string $reason):void {if(!$condition)throw new RuntimeException($reason);}
function nf_plan(string $path):array {
 nf_check(hash_file('sha256',$path)===NF_PLAN_SHA,'Frozen plan SHA mismatch');
 $p=json_decode(file_get_contents($path),true,512,JSON_THROW_ON_ERROR);
 nf_check($p['execution_counts_actual']===['DDL'=>0,'DML'=>0,'business_HTTP'=>0,'new_connections'=>0],'Proposal actual-count claim changed');
 return $p;
}
function nf_shape(array $spec,string $lane):void {
 nf_check(in_array($lane,['B','C'],true),'Unknown lane');
 nf_check($spec['table_name']==='CDX_NF5_261011_D9A4'&&$spec['marker']==='CDXNF5D9A4','Exact own object/marker');
 nf_check($spec['max_live_rows']===1&&count($spec['steps'])===48,'Exact case/row budget');
 $ins=0;$upd=0;
 foreach($spec['steps']as $case){
  nf_check(strpos($case['transaction'],'ROLLBACK')!==false,'Missing rollback');
  foreach($case['DML']as $d){
   nf_check(substr_count($d['sql'],'?')===count($d['bindings']),'Placeholder mismatch');
   nf_check(stripos($d['sql'],'cdx_nf5_261011_d9a4')!==false&&!preg_match('/YDY_|DELETE|ALTER|DROP|TRUNCATE|COMMIT/i',$d['sql']),'DML target/action outside scope');
   if(strpos($d['sql'],'INSERT ')===0)$ins++;elseif(strpos($d['sql'],'UPDATE ')===0)$upd++;else nf_check(false,'Unknown DML');
   foreach($d['bindings']as $b)nf_check(in_array($b['pdo_type_code'],[0,1,2],true),'PDO type');
  }
 }
 nf_check($ins===48&&$upd===24&&$spec['budget']['total_DML_attempts']===72,'Exact72 DML budget');
 nf_check($spec['fingerprint_gate']['expected_ordinary']===true,'Ordinary only');
}
function nf_keyrows(array $rows):array {return array_map(static function($r){return array_change_key_case($r,CASE_LOWER);},$rows);}
function nf_fingerprint_gate(array $meta,string $lane):void {
 $cols=nf_keyrows($meta[0]);$names=array_map('strtoupper',array_column($cols,'column_name'));
 nf_check($names===['ID','MARKER','POWER','CARBOHYDRATE','PROTEIN','FAT','SODIUM'],'Exact seven ordered columns');
 foreach($cols as $i=>$c){
  if($lane==='B'){
   nf_check($c['column_default']===null,'B exact defaultNULL/no implicit default');
   nf_check($c['is_nullable']===($i<2?'NO':'YES'),'B exact nullability');
   if($i===0)nf_check(preg_match('/^int(?:\(\d+\))?$/i',$c['column_type'])===1&&$c['extra']==='','B ID no auto_increment/extra');
   elseif($i===1)nf_check(strtolower($c['column_type'])==='varchar(16)','B marker length');
   else nf_check(strtolower($c['column_type'])==='float(8,2)'&&$c['column_default']===null&&$c['extra']==='','B FLOAT(8,2) nullable defaultNULL no extra');
  }else{
   nf_check($c['data_default']===null,'C exact defaultNULL/no implicit default');
   nf_check($c['nullable']===($i<2?'N':'Y'),'C exact nullability');
   if($i===0)nf_check(strtoupper($c['data_type'])==='INTEGER'||strtoupper($c['data_type'])==='INT','C ID int');
   elseif($i===1)nf_check(strtoupper($c['data_type'])==='VARCHAR'&&(int)$c['data_length']>=16&&(int)$c['data_length']<=64,'C marker VARCHAR16 byte/char representation');
   else nf_check(strtoupper($c['data_type'])==='FLOAT'&&(int)$c['data_length']===8&&(int)$c['data_precision']===53&&(int)$c['data_scale']===0&&$c['data_default']===null,'C FLOAT53 nullable defaultNULL');
  }
 }
 $indexes=nf_keyrows($meta[1]);nf_check(count($indexes)===1,'Exactly one implicit PK index');
 if($lane==='B'){
  nf_check(strtoupper($indexes[0]['index_name'])==='PRIMARY'&&(int)$indexes[0]['non_unique']===0&&strtoupper($indexes[0]['column_name'])==='ID','B PK index ID only');
  $constraints=nf_keyrows($meta[2]);nf_check(count($constraints)===1&&strtoupper($constraints[0]['constraint_type'])==='PRIMARY KEY','B no unexpected constraints');
  $keys=nf_keyrows($meta[3]);nf_check(count($keys)===1&&strtoupper($keys[0]['column_name'])==='ID'&&$keys[0]['referenced_table_name']===null,'B PK only/no inbound/outbound FK');
  nf_check($meta[4]===[],'B no triggers');$ddl=$meta[5][0]['create table']??null;
 }else{
  nf_check(strtoupper($indexes[0]['uniqueness'])==='UNIQUE','C unique PK index');
  $ic=nf_keyrows($meta[2]);nf_check(count($ic)===1&&strtoupper($ic[0]['column_name'])==='ID','C PK index ID only');
  $constraints=nf_keyrows($meta[3]);nf_check(count($constraints)===1&&strtoupper($constraints[0]['constraint_type'])==='P'&&strtoupper($constraints[0]['status'])==='ENABLED','C exact one enabled PK/no unexpected constraints/FK');
  $cc=nf_keyrows($meta[4]);nf_check(count($cc)===1&&strtoupper($cc[0]['column_name'])==='ID','C PK constraint ID only');
  nf_check($meta[5]===[],'C no triggers');$ddl=$meta[6][0]['ddl']??null;
 }
 nf_check(is_string($ddl)&&stripos($ddl,'CDX_NF5_261011_D9A4')!==false&&stripos($ddl,'CREATE TABLE')!==false&&stripos($ddl,'TEMPORARY')===false,'Actual own ordinary CREATE definition');
 nf_check(preg_match('/["`]MARKER["`]\s+VARCHAR\(16(?:\s+CHAR|\s+BYTE)?\)/i',$ddl)===1,'Actual declared marker VARCHAR16 exact');
}
function nf_natural_gate(array $snapshot,string $lane):void {
 $rows=nf_keyrows($snapshot[6]);$seen=[];
 foreach($rows as $r){
  $t=strtoupper($r['table_name']);$n=strtoupper($r['column_name']);
  if(in_array($t,['YDY_NUTRITION','YDY_NUTRITION_TEMPLATE'],true)&&in_array($n,['POWER','CARBOHYDRATE','PROTEIN','FAT','SODIUM'],true)){
   if($lane==='B')nf_check(strtolower($r['column_type'])==='float(8,2)'&&$r['is_nullable']==='YES'&&$r['column_default']===null,'Fresh B nutrition FLOAT(8,2) definition');
   else nf_check(strtoupper($r['data_type'])==='FLOAT'&&(int)$r['data_length']===8&&(int)$r['data_precision']===53&&(int)$r['data_scale']===0&&$r['nullable']==='Y'&&$r['data_default']===null,'Fresh C nutrition FLOAT53 definition');
   $seen[$t.'.'.$n]=true;
  }
 }
 nf_check(count($seen)===10,'Fresh product/template five-column type family10');
}
function nf_num($value,int $expected,string $label):void {nf_check((is_int($value)||is_float($value)||(is_string($value)&&is_numeric($value)))&&(string)(int)$value===(string)$expected&&(float)$value===(float)$expected,$label);}
function nf_domain(PDOException $e,string $lane):bool {
 $state=(string)$e->getCode();$native=$e->errorInfo[1]??null;
 return substr($state,0,2)==='22'||($lane==='B'&&in_array((int)$native,[1264,1048,1366],true));
}
