<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');
$lane=getenv('PILOT_LANE');$root=getenv('PILOT_TARGET_ROOT');$runtime=getenv('PILOT_RUNTIME');
$marker=getenv('HELP_MARKER');$base=getenv('POC_BASE_URL');$dm=getenv('POC_DIALECT')==='dm';
if(!in_array($lane,['B','C'],true)||!preg_match('#^http://127\.0\.0\.1:1827[12]$#',$base)||!preg_match('/^CDXHP10[A-F0-9]{12}$/D',$marker))exit(78);
define('TP_ENV','poc');define('BUSINESS','aizhct');define('ENTERPRISE_NO','10001');
$loader=require $root.'/vendor/autoload.php';$overlay=getenv('TERMINAL_SOURCE_OVERLAY');
$loader->setPsr4('app\\',$overlay.'/app');$loader->setPsr4('cores\\',$overlay.'/cores');require $root.'/external_stubs.php';
$app=new \think\App($root);$app->setEnvName('poc');$app->setRuntimePath($runtime.'/runner/');$app->initialize();
$app->config->set(['default'=>'file','stores'=>['file'=>['type'=>'File','path'=>$runtime.'/cache/','prefix'=>'','expire'=>0]]],'cache');
$newPdo=static function()use($dm){$dsn=$dm?'dm:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';schema='.getenv('POC_AI_SCHEMA'):'mysql:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';dbname='.getenv('POC_AI_SCHEMA').';charset=utf8';return new PDO($dsn,getenv('POC_AI_USER'),getenv('POC_AI_PASSWORD'),[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);};
$pdo=$newPdo();$table=$dm?'YOSHOP_HELP':'yoshop_help';
$q=static function(string $sql,array $args=[])use($pdo):array{$s=$pdo->prepare($sql);$s->execute($args);$rows=$s->fetchAll(PDO::FETCH_ASSOC);foreach($rows as &$r)foreach($r as &$v)if(is_resource($v))$v=stream_get_contents($v);return $rows;};
$hash=static function($v):string{return hash('sha256',json_encode($v,JSON_UNESCAPED_UNICODE));};
$natural=static function()use($q,$table,$marker,$hash):array{$rows=$q('SELECT * FROM '.$table.' WHERE TITLE NOT LIKE ? ORDER BY HELP_ID',[$marker.'%']);return ['rows'=>count($rows),'sha256'=>$hash($rows)];};
$before=$natural();$initialOwn=$q('SELECT COUNT(*) AS N FROM '.$table.' WHERE TITLE LIKE ?',[$marker.'%'])[0]['n'];
if((int)$initialOwn!==0)throw new RuntimeException('Marker reused');
$triggerRows=$dm?$q("SELECT TRIGGER_NAME FROM USER_TRIGGERS WHERE TABLE_NAME='YOSHOP_HELP'"):$q("SHOW TRIGGERS WHERE `Table`='yoshop_help'");
if(count($triggerRows)>0)throw new RuntimeException('Help triggers require extra side-effect review');
$schema=$dm?$q("SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YOSHOP_HELP' ORDER BY COLUMN_ID"):$q('SHOW COLUMNS FROM yoshop_help');
$users=$q('SELECT * FROM '.($dm?'YOSHOP_STORE_USER':'yoshop_store_user').' WHERE STORE_ID=10001 AND IS_DELETE=0 AND IS_SUPER=1');
if(!$users)throw new RuntimeException('No read-only super-user identity for private cache');
$user=$users[0];$user['store_id']=(int)$user['store_id'];$user['store_user_id']=(int)$user['store_user_id'];$token=bin2hex(random_bytes(24));
\think\facade\Cache::set($token,['user'=>$user,'store_id'=>10001,'is_login'=>true],3600);
$boundary=json_decode(file_get_contents($base.'/__poc_boundary'),true);
if(empty($boundary['external_stubs'])||empty($boundary['own_document_crud_allowlist']))throw new RuntimeException('Side-effect boundary absent');
file_get_contents($base.'/store/content.Help/add',false,stream_context_create(['http'=>['method'=>'GET','ignore_errors'=>true]]));if(strpos($http_response_header[0]??'','403')===false)throw new RuntimeException('Method boundary absent');
file_get_contents($base.'/store/content.Help/add',false,stream_context_create(['http'=>['method'=>'POST','ignore_errors'=>true,'header'=>'Content-Type: application/x-www-form-urlencoded','content'=>'form[title]=NOT_OWN&form[content]=blocked']]));if(strpos($http_response_header[0]??'','403')===false)throw new RuntimeException('Own-document guard absent');
file_put_contents($runtime.'/ai_queries.jsonl','');chmod($runtime.'/ai_queries.jsonl',0600);
$responses=[];$cases=[];$idMap=[];$fixtureRows=[];
$request=static function($name,$method,$path,$args=[],$form=null)use($base,$token,&$responses):array{$url=$base.$path.'?'.http_build_query(['storeId'=>10001]+$args);$opts=['method'=>$method,'ignore_errors'=>true,'timeout'=>30,'header'=>"Platform: POC\r\nAccess-Token: ".$token."\r\nX-Pilot-Scenario: PILOT_AI_HELP_".$name."\r\n"];if($form!==null){$opts['header'].="Content-Type: application/x-www-form-urlencoded\r\n";$opts['content']=http_build_query(['form'=>$form]);}$raw=file_get_contents($url,false,stream_context_create(['http'=>$opts]));preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$body=json_decode((string)$raw,true);$result=['http_status'=>(int)($m[1]??0),'body'=>$body];$responses[$name]=$result;return $result;};
$ownRow=static function($title)use($q,$table):?array{$r=$q('SELECT * FROM '.$table.' WHERE STORE_ID=10001 AND TITLE=?',[$title]);return count($r)===1?$r[0]:null;};
$check=static function($row,$title,$content,$deleted):bool{return is_array($row)&&$row['title']===$title&&$row['content']===htmlspecialchars(trim($content),ENT_COMPAT)&&(int)$row['is_delete']===$deleted&&(int)$row['store_id']===10001;};
$httpSource=static function($item,$source):bool{if(!is_array($item)||!is_array($source))return false;foreach($source as $field=>$value){if(!array_key_exists($field,$item))return false;if(in_array($field,['create_time','update_time'],true))$value=date('Y-m-d H:i:s',(int)$value);if(is_numeric($value)&&is_numeric($item[$field])){if(bccomp((string)$value,(string)$item[$field],0)!==0)return false;}elseif($value!==$item[$field])return false;}return true;};
$fixture=static function($row):array{unset($row['content']);return $row;};
$contentCases=['EMPTY'=>'','QUOTED'=>"帮助中文 '单引号' \"双引号\" \\ 纯文本\n第二行",'NORMAL'=>str_repeat('正常帮助文档内容。',20)];
foreach([32767,32768,32769,49152] as $bytes)$contentCases['BYTES_'.$bytes]=str_repeat('A',$bytes-16).'tail0123456789AB';
try{
foreach(['store','terminal'] as $module){
    foreach($contentCases as $label=>$content){
        $name=strtoupper($module).'_'.$label;$title=$marker.'_'.$name;$path='/'.$module.'/content.Help/';
        $r=$request($name.'_ADD','POST',$path.'add',[],['title'=>$title,'content'=>$content,'sort'=>0]);$row=$ownRow($title);
        $ok=$r['http_status']===200&&($r['body']['status']??null)===200&&$check($row,$title,$content,0);
        $cases[]=['scenario'=>$name.'_ADD','method'=>'POST','path'=>$path.'add','positive'=>$ok,'content_bytes'=>strlen($content),'content_sha256'=>hash('sha256',$content),'source_equal'=>$check($row,$title,$content,0)];
        if(!$ok)continue;
        $id=(int)$row['help_id'];$idMap[$name]=$id;
        $rank=(int)$q('SELECT COUNT(*) AS N FROM '.$table.' WHERE STORE_ID=10001 AND IS_DELETE=0 AND (SORT<0 OR (SORT=0 AND HELP_ID<=?))',[$id])[0]['n'];$page=max(1,(int)ceil($rank/15));
        $r=$request($name.'_LIST','GET',$path.'list',['page'=>$page]);$found=[];foreach($r['body']['data']['list']['data']??[] as $item)if((int)($item['help_id']??0)===$id)$found[]=$item;
        $match=count($found)===1&&$check($found[0],$title,$content,0)&&$httpSource($found[0],$row);
        $fixtureRows[$name.'_LIST']=$fixture($row);
        $cases[]=['scenario'=>$name.'_LIST','method'=>'GET','path'=>$path.'list','positive'=>$r['http_status']===200&&($r['body']['status']??null)===200&&$match,'content_bytes'=>strlen($content),'content_sha256'=>hash('sha256',$content),'source_equal'=>$match,'requested_page'=>$page];
        $updated=$content===''?'编辑中文 " 引号':($label==='QUOTED'?"更新 '帮助' \\ 测试":strrev($content));
        // Reversing UTF-8 bytes is invalid; the normal Chinese case uses valid new Chinese text.
        if($label==='NORMAL')$updated=str_repeat('已更新帮助文档。',20);
        $r=$request($name.'_EDIT','POST',$path.'edit',['helpId'=>$id],['title'=>$title,'content'=>$updated,'sort'=>0]);$row=$ownRow($title);
        $match=$check($row,$title,$updated,0);
        $cases[]=['scenario'=>$name.'_EDIT','method'=>'POST','path'=>$path.'edit','positive'=>$r['http_status']===200&&($r['body']['status']??null)===200&&$match,'content_bytes'=>strlen($updated),'content_sha256'=>hash('sha256',$updated),'source_equal'=>$match];
        $r=$request($name.'_LIST_EDITED','GET',$path.'list',['page'=>$page]);$found=[];foreach($r['body']['data']['list']['data']??[] as $item)if((int)($item['help_id']??0)===$id)$found[]=$item;
        $match=count($found)===1&&$check($found[0],$title,$updated,0)&&$httpSource($found[0],$row);
        $fixtureRows[$name.'_LIST_EDITED']=$fixture($row);
        $cases[]=['scenario'=>$name.'_LIST_EDITED','method'=>'GET','path'=>$path.'list','positive'=>$r['http_status']===200&&($r['body']['status']??null)===200&&$match,'content_bytes'=>strlen($updated),'content_sha256'=>hash('sha256',$updated),'source_equal'=>$match,'requested_page'=>$page];
        $r=$request($name.'_DELETE','POST',$path.'delete',['helpId'=>$id]);$row=$ownRow($title);$match=$check($row,$title,$updated,1);
        $cases[]=['scenario'=>$name.'_DELETE','method'=>'POST','path'=>$path.'delete','positive'=>$r['http_status']===200&&($r['body']['status']??null)===200&&$match,'source_equal'=>$match];
        $r=$request($name.'_LIST_DELETED','GET',$path.'list',['page'=>$page]);$present=false;foreach($r['body']['data']['list']['data']??[] as $item)if((int)($item['help_id']??0)===$id)$present=true;
        $cases[]=['scenario'=>$name.'_LIST_DELETED','method'=>'GET','path'=>$path.'list','positive'=>$r['http_status']===200&&($r['body']['status']??null)===200&&!$present,'source_equal'=>!$present];
    }
}
}finally{
    $stmt=$pdo->prepare('DELETE FROM '.$table.' WHERE STORE_ID=10001 AND TITLE LIKE ?');$stmt->execute([$marker.'%']);$cleaned=$stmt->rowCount();
    \think\facade\Cache::delete($token);
}
$audit=$newPdo();$stmt=$audit->prepare('SELECT COUNT(*) FROM '.$table.' WHERE TITLE LIKE ?');$stmt->execute([$marker.'%']);$remaining=(int)$stmt->fetchColumn();
$after=$natural();$events=[];foreach(file($runtime.'/ai_queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$events[]=json_decode($line,true);
$sources=[];foreach(['app/store/controller/Controller.php','app/store/controller/content/Help.php','app/store/model/Help.php','app/store/service/Auth.php','app/terminal/controller/Controller.php','app/terminal/controller/content/Help.php','app/terminal/model/Help.php','app/common/model/Help.php','app/common/service/store/User.php','cores/Request.php','cores/BaseController.php','cores/BaseModel.php'] as $file)$sources[$file]=hash_file('sha256',$overlay.'/'.$file);
foreach(['my_trim','my_htmlspecialchars'] as $function){$r=new ReflectionFunction($function);$lines=file($r->getFileName());$sources['function:'.$function]=hash('sha256',implode('',array_slice($lines,$r->getStartLine()-1,$r->getEndLine()-$r->getStartLine()+1)));}
$report=['lane'=>$lane,'baseline'=>'d412aeff9bfb828f56a5f87ca24acd4e029efa40','cases'=>$cases,'case_count'=>count($cases),'failures'=>count(array_filter($cases,static function($c){return !$c['positive'];})),'boundary_verified'=>true,'trigger_count'=>count($triggerRows),'schema'=>$schema,'natural_before'=>$before,'natural_after'=>$after,'natural_unchanged'=>$before===$after,'owned_records_physically_cleaned'=>$cleaned,'independent_remaining'=>$remaining,'token_cleaned'=>!\think\facade\Cache::has($token),'source_sha256'=>$sources,'events'=>$events,'acceptance'=>'specified marked-document CRUD and text boundaries only'];
file_put_contents($runtime.'/report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));chmod($runtime.'/report.json',0600);
file_put_contents($runtime.'/responses.json',json_encode($responses,JSON_UNESCAPED_UNICODE));chmod($runtime.'/responses.json',0600);
file_put_contents($runtime.'/fixture_map.json',json_encode(['marker'=>$marker,'ids'=>$idMap,'source_rows'=>$fixtureRows]));chmod($runtime.'/fixture_map.json',0600);
echo json_encode(['lane'=>$lane,'cases'=>count($cases),'failures'=>$report['failures'],'unchanged'=>$before===$after,'remaining'=>$remaining,'token_cleaned'=>$report['token_cleaned'],'runtime'=>$runtime]),"\n";
exit($report['failures']||$before!==$after||$remaining?1:0);
