<?php
declare(strict_types=1);
require_once __DIR__.'/common.php';
function catSha():array{$out=[];foreach(file(__DIR__.'/SOURCE_SHA256.txt',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $line){[$sha,$file]=preg_split('/\s+/',$line,2);if(hash_file('sha256',getenv('SM7_ROOT').'/'.$file)!==$sha){throw new RuntimeException('Frozen source drift: '.$file);}$out[$file]=$sha;}return $out;}
function catLocalConfig():array{
    // Resolve only literal base defaults needed by this connection without changing
    // the HTTP process's APP_PATH/TP_ENV globals or loading a production variant.
    $base='/workspace/zhct/zhctproject/store/application/database.php';$local='/workspace/zhct/zhctproject/store/application/config/local/database.php';$sha=[];foreach(file(__DIR__.'/SOURCE_SHA256.txt',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $line){[$hash,$file]=preg_split('/\s+/',$line,2);$sha[$file]=$hash;}if(hash_file('sha256',$base)!==$sha['application/database.php']||hash_file('sha256',$local)!==$sha['application/config/local/database.php']){throw new RuntimeException('Actual code local config changed');}$text=file_get_contents($base);$defaults=[];foreach(['type','hostport','charset'] as $key){if(!preg_match('/^\s*\x27'.preg_quote($key,'/').'\x27\s*=>\s*\x27([^\x27]+)\x27\s*,/m',$text,$m)){throw new RuntimeException('Unknown literal connection default');}$defaults[$key]=$m[1];}return array_merge($defaults,require $local);
}
function catAiConfig():array{$lane=getenv('SM7_LANE');if($lane==='C'){$cfg=['type'=>'app\\cores\\database\\Dm','hostname'=>getenv('POC_AI_HOST'),'hostport'=>getenv('POC_AI_PORT'),'database'=>getenv('POC_AI_SCHEMA'),'username'=>getenv('POC_AI_USER'),'password'=>getenv('POC_AI_PASSWORD'),'prefix'=>'yoshop_','charset'=>'utf8'];if($cfg['hostname']!=='127.0.0.1'||(string)$cfg['hostport']!=='5238'||$cfg['database']!=='AIZHCT_APP'||$cfg['username']!=='AIZHCT_APP'||!is_string($cfg['password'])||$cfg['password']===''){throw new RuntimeException('AI DM allowlist');}return $cfg;}
    $local=catLocalConfig();$parent=require getenv('SM7_PARENT').'/application/database.php';$actual=array_merge($parent,$parent['ai_zhct']);$expected=array_merge($local,$local['ai_zhct']);foreach(['hostname','database','username','password','hostport','prefix','charset'] as $k){$a=$actual[$k]??null;$b=$expected[$k]??null;if($k==='hostport'){if(!ctype_digit((string)$a)||!ctype_digit((string)$b)||(int)$a<1||(int)$a>65535){throw new RuntimeException('Invalid AI source port');}$a=(string)(int)$a;$b=(string)(int)$b;}if($a!==$b){throw new RuntimeException('AI not code local effective connection');}}return $expected;
}
function catAiPdo():PDO{$cfg=catAiConfig();$dm=getenv('SM7_LANE')==='C';$dsn=$dm?'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP':'mysql:host='.$cfg['hostname'].';port='.(int)$cfg['hostport'].';dbname='.$cfg['database'].';charset='.$cfg['charset'];$options=$dm?[]:[PDO::ATTR_STRINGIFY_FETCHES=>false,PDO::ATTR_EMULATE_PREPARES=>false];$db=new PDO($dsn,$cfg['username'],$cfg['password'],$options+[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);if($dm&&$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()!=='AIZHCT_APP'){throw new RuntimeException('Effective AI schema mismatch');}if(!$dm&&(strpos($db->query('SELECT VERSION()')->fetchColumn(),'5.6.')!==0||$db->query('SELECT DATABASE()')->fetchColumn()!==$cfg['database'])){throw new RuntimeException('Effective local AI identity mismatch');}return $db;}
function catActor(PDO $db):array{$a=smLoad('actor-private.json');$baseline=smLoad('actor-baseline-private.json');$rows=smRows($db,'SELECT * FROM ydy_user WHERE id=? AND uuid=? AND account=? AND create_by=?',[$a['id'],$a['uuid'],$a['marker'],$a['marker']]);$digest=smDigest($rows);if($digest!==$baseline['actor_digest']){throw new RuntimeException('Owned disabled actor changed');}return [['id'=>$rows[0]['id'],'uuid'=>$rows[0]['uuid'],'account'=>$rows[0]['account'],'type'=>$rows[0]['type'],'status'=>$rows[0]['status'],'full_row_digest'=>$digest]];}
function catCount(PDO $db,string $sql,array $args=[]):int{$r=smRows($db,$sql,$args);if(count($r)!==1||!isset($r[0]['n'])||!is_numeric($r[0]['n'])){throw new RuntimeException('Count contract');}return (int)$r[0]['n'];}
function catStrict($a,$b,string $path='$',array &$diff=[]):array{if(gettype($a)!==gettype($b)){$diff[]=['path'=>$path,'kind'=>'type','expected_type'=>gettype($a),'actual_type'=>gettype($b)];return $diff;}if(is_object($a)){$a=get_object_vars($a);$b=get_object_vars($b);ksort($a);ksort($b);}if(is_array($a)){if(array_keys($a)!==array_keys($b)){$diff[]=['path'=>$path,'kind'=>'keys'];}foreach($a as $k=>$v){if(array_key_exists($k,$b)){catStrict($v,$b[$k],$path.'.'.$k,$diff);}}}elseif($a!==$b){$diff[]=['path'=>$path,'kind'=>'value','expected_type'=>gettype($a),'actual_type'=>gettype($b)];}return $diff;}
