<?php
declare(strict_types=1);
$lane=getenv('ACT_LANE');$root=getenv('ACT_ROOT');$plan=json_decode(file_get_contents(__DIR__.'/plan.json'),true);if(!in_array($lane,['B','C'],true)||$plan['marker']!=='CDXACT11W2'||$plan['authorization']!=='ROOT_APPROVED_EXACT_ACTIVITY_FIXED')throw new RuntimeException('Pre-PDO scope');
if($plan['runtime']!==__DIR__||$plan['ports']!==['B'=>18993,'C'=>18994]||$plan['total_readonly_hard_cap_per_side']!==800)throw new RuntimeException('Exclusive runtime and unified read scope');
if(hash_file('sha256',__DIR__.'/source-lock.json')!==$plan['source_sha256'])throw new RuntimeException('Root source mapping');$lock=json_decode(file_get_contents(__DIR__.'/source-lock.json'),true);$actual=[];$iter=new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__.'/source/application',FilesystemIterator::SKIP_DOTS));foreach($iter as $f){if($f->isLink())throw new RuntimeException('Source symlink');if($f->isFile()){$rel=substr($f->getPathname(),strlen(__DIR__.'/source/application/'));if(strpos(basename($rel),'._')===0)throw new RuntimeException('AppleDouble');$actual[$rel]=hash_file('sha256',$f->getPathname());}}ksort($lock);ksort($actual);if(count($actual)!==959||$actual!==$lock)throw new RuntimeException('APP959 exact source');
defined('TP_ENV')||define('TP_ENV','local');defined('BUSINESS')||define('BUSINESS','');defined('APP_PATH')||define('APP_PATH',__DIR__.'/source/application/');defined('ROOT_PATH')||define('ROOT_PATH',$root.'/');
$cfg=(static function($f){return require $f;})($root.'/application/database.php');$ai=$cfg;foreach(['hostname','hostport','database','username','password','prefix']as $k)$ai[$k]=$cfg['ai_zhct'][$k]??$cfg[$k];if($cfg['prefix']!=='ydy_'||$ai['prefix']!=='yoshop_')throw new RuntimeException('Dual DB prefixes');
if($lane==='B'){$local=(static function($f){return require $f;})('/workspace/zhct/zhctproject/store/application/config/local/database.php');foreach(['hostname','database','username','password']as $k)if($cfg[$k]!==$local[$k]||$ai[$k]!==$local['ai_zhct'][$k])throw new RuntimeException('Original code-local dual MySQL');}
else{foreach([[$cfg,5237,'ZHCT_APP'],[$ai,5238,'AIZHCT_APP']]as $c)if($c[0]['hostname']!=='127.0.0.1'||(int)$c[0]['hostport']!==$c[1]||$c[0]['database']!==$c[2]||$c[0]['username']!==$c[2])throw new RuntimeException('Loopback DM pair');}
$options=[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER,PDO::ATTR_ORACLE_NULLS=>PDO::NULL_NATURAL,PDO::ATTR_STRINGIFY_FETCHES=>false,PDO::ATTR_EMULATE_PREPARES=>false];
$dsn=static function($c)use($lane){return $lane==='B'?'mysql:host='.$c['hostname'].';port='.($c['hostport']?:3306).';dbname='.$c['database'].';charset='.$c['charset']:'dm:host=127.0.0.1;port='.$c['hostport'].';schema='.$c['database'];};
$save=static function($name,$value){$f=__DIR__.'/private/'.$name;$h=fopen($f,'xb');if(!$h)throw new RuntimeException('Immutable record collision '.$name);$j=json_encode($value,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION);if($j===false)throw new RuntimeException('JSON encode');fwrite($h,$j);fclose($h);chmod($f,0600);};
$budget=static function($kind,$max)use($lane){if(in_array($kind,['observer','business-read'],true)){$tf=__DIR__.'/private/budget-total-read.json';$th=fopen($tf,'c+');flock($th,LOCK_EX);$old=stream_get_contents($th);$total=$old!==''?(int)$old:(int)@file_get_contents(__DIR__.'/private/budget-observer.json')+(int)@file_get_contents(__DIR__.'/private/budget-business-read.json');if($total>=800)throw new RuntimeException('Unified readonly ceiling');rewind($th);ftruncate($th,0);fwrite($th,(string)($total+1));flock($th,LOCK_UN);fclose($th);chmod($tf,0600);}$f=__DIR__.'/private/budget-'.$kind.'.json';$h=fopen($f,'c+');flock($h,LOCK_EX);$n=(int)stream_get_contents($h);if($n>=$max)throw new RuntimeException('Budget '.$kind);rewind($h);ftruncate($h,0);fwrite($h,(string)($n+1));flock($h,LOCK_UN);fclose($h);chmod($f,0600);};
$db=new PDO($dsn($cfg),$cfg['username'],$cfg['password'],$options);$adb=new PDO($dsn($ai),$ai['username'],$ai['password'],$options);$GLOBALS['ACT_OBSERVER']=[];
$q=static function($which,$sql,$args=[],$digest=false)use($db,$adb,$budget){if(!preg_match('/^SELECT\b/',$sql)||strpos($sql,';')!==false)throw new RuntimeException('Read-only observer');$budget('observer',600);$s=($which==='ai'?$adb:$db)->prepare($sql);foreach($args as $i=>$v)$s->bindValue($i+1,$v,is_int($v)?1:($v===null?0:2));$s->execute();$rows=[];$ctx=hash_init('sha256');$n=0;while($r=$s->fetch(PDO::FETCH_ASSOC)){foreach($r as &$v)if(is_resource($v))$v=stream_get_contents($v);unset($v);$r=array_change_key_case($r,CASE_LOWER);$n++;hash_update($ctx,serialize($r));if(!$digest)$rows[]=$r;}$value=$digest?['rows'=>$n,'typed_sha256'=>hash_final($ctx)]:$rows;$GLOBALS['ACT_OBSERVER'][]=['connection'=>$which,'sql'=>$s->queryString,'binds'=>$args,'result'=>$value,'rowCount'=>$s->rowCount()];return $value;};
$source=static function($full=false)use($q,$lane){$r=[];foreach(['activity','activity_register','activity_type']as $t)$r[$t]=$q('ai','SELECT * FROM yoshop_'.$t.' ORDER BY id');if($full)$r['ai_users']=$q('ai','SELECT * FROM yoshop_user ORDER BY user_id',[],true);$r['actor']=$q('store','SELECT * FROM ydy_user WHERE id=?',[$lane==='B'?97260:2791]);if($full)$r['config']=$q('store','SELECT * FROM ydy_config ORDER BY id',[],true);foreach(['nutrition','nutrition_template','nutrition_set','nutrition_category','nutrition_time']as $t)$r[$t]=$q('store','SELECT * FROM ydy_'.$t.' WHERE name LIKE ? ORDER BY id',['CDXNF11F5%']);$uuids=array_column($r['nutrition_template'],'uuid');$r['nutrition_template_ext']=$uuids?$q('store','SELECT * FROM ydy_nutrition_template_ext WHERE template_uuid IN ('.implode(',',array_fill(0,count($uuids),'?')).') ORDER BY id',$uuids):[];return $r;};
$guard=static function($before,$after,$own=null){if(!array_key_exists('ai_users',$before)||!array_key_exists('ai_users',$after)){unset($before['ai_users'],$before['config'],$after['ai_users'],$after['config']);}if($own!==null){$strip=static function($s)use($own){$s['activity']=array_values(array_filter($s['activity'],static function($r)use($own){return $r['uuid']!==$own;}));return $s;};$before=$strip($before);$after=$strip($after);}if($before!==$after)throw new RuntimeException('Protected full-column state drift');};
