<?php
declare(strict_types=1);
require __DIR__.'/bootstrap.php';if(($argv[1]??'')==='audit'){echo json_encode(['lane'=>$lane,'owned_rows'=>$counts()]),"\n";exit($counts()?1:0);}
$check($counts()===0,'Address collision');$before=$snapshot();$started=time();$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);
$check(($boundary['allowlist']??'')==='store-address-only'&&($boundary['address_model_sha']??'')==='01e564a1666c1c2a51b1a77d2fee25b9f39e1b0d33c4184fa236bcb218316bcc'&&($boundary['region_model_sha']??'')==='638d8520e1f8a0696580c0c6f2c23e175e97c68f934a58dc9e924605ab960f3a','Actual address/region source boundary');
$root=(string)getenv('CATEGORY_ROOT');define('TP_ENV','poc');define('BUSINESS','aizhct');define('ENTERPRISE_NO','10001');require $root.'/vendor/autoload.php';$app=new \think\App($root);$app->setEnvName('poc');$app->setRuntimePath($runtime.'/fixture-runtime/');$app->initialize();
$cache=new \think\cache\driver\File($app,['path'=>$runtime.'/cache/','prefix'=>'','expire'=>0]);$check($cache->get('region')===null,'Fresh private region cache required');$tokens=[];
foreach($tenants as $module=>$tenant){$tokens[$module]=bin2hex(random_bytes(24));$cache->set($tokens[$module],['user'=>['store_user_id'=>$tenant,'store_id'=>$tenant,'real_name'=>$marker,'user_name'=>$marker,'is_super'=>1],'store_id'=>$tenant,'is_login'=>true],1800);}
$map=['rows'=>[]];file_put_contents($runtime.'/private-owned-map.json',json_encode($map));foreach(['queries.jsonl','write-audit.jsonl','private-write-sql.jsonl'] as $file)file_put_contents($runtime.'/'.$file,'');
$regionRows=$query('SELECT id,pid,name,'.($dm?'"LEVEL"':'`level`').' AS level FROM yoshop_region ORDER BY id');$region=[];foreach($regionRows as $row){foreach(['id','pid','level'] as $key)$row[$key]=(int)$row[$key];$region[$row['id']]=$row;}
$cases=[];$proofs=[];$ids=[];$times=[];$responses=[];$pending=[];$error=null;$cacheProof=null;
$deep=static function($a,$b,string $path='$')use(&$deep,$check){if(is_array($b)){$check(is_array($a),'Array '.$path);$ak=array_keys($a);$bk=array_keys($b);sort($ak);sort($bk);$check($ak===$bk,'Full keys '.$path);foreach($b as $k=>$v)$deep($a[$k],$v,$path.'.'.$k);}else $check($a===$b,'Value/type '.$path);};
$shape=static function($v)use(&$shape){if(is_object($v)){$o=[];foreach(get_object_vars($v) as $k=>$x)$o[$k]=$shape($x);ksort($o);return ['object'=>$o];}if(is_array($v))return ['array'=>array_map($shape,$v)];return gettype($v);};
$project=static function(array $row,bool $append=true)use($check,$started,$region,&$times):array{$out=$row;foreach($out as $key=>&$v){if(in_array($key,['address_id','type','province_id','city_id','region_id','sort','is_delete','store_id'],true))$v=(int)$v;
    elseif(in_array($key,['create_time','update_time'],true)){$n=(int)$v;$check($n>=$started-2&&$n<=time()+2,'Address timestamp execution window');$v=$append?date('Y-m-d H:i:s',$n):$n;}}unset($v);
    $id=(int)$row['address_id'];if(isset($times[$id]))$check((int)$row['create_time']===$times[$id][0]&&(int)$row['update_time']>=$times[$id][1],'Address time invariants');$times[$id]=[(int)$row['create_time'],(int)$row['update_time']];
    if($append){$out['cascader']=[$out['province_id'],$out['city_id'],$out['region_id']];$out['region']=[];foreach(['province','city','region'] as $part){$rid=$out[$part.'_id'];$out['region'][$part]=$rid===0?'其他':($region[$rid]['name']??'其他');}$out['full_address']=implode('',$out['region']).$out['detail'];}return $out;};
$request=static function(string $module,string $name,string $action,array $args=[],?array $form=null)use($base,$tokens,$shape,$check,&$responses):array{$scenario='PILOT_ADR_'.strtoupper($module.'_'.$name);$method=in_array($action,['list','all'],true)?'GET':'POST';$url=$base.'/'.$module.'/store.Address/'.$action;if($method==='GET'&&$args)$url.='?'.http_build_query($args);$body=$args;if($form!==null)$body['form']=$form;
    $headers='Host: '.$module.".address-test.invalid\r\nAccess-Token: ".$tokens[$module]."\r\nPlatform: POC\r\nX-Pilot-Scenario: ".$scenario."\r\nContent-Type: application/json\r\n";
    $raw=file_get_contents($url,false,stream_context_create(['http'=>['method'=>$method,'header'=>$headers,'content'=>$method==='GET'?'':json_encode($body,JSON_UNESCAPED_UNICODE),'timeout'=>30,'ignore_errors'=>true]]));$check(is_string($raw)&&strlen($raw)<1048576,'Address wire transport');$json=json_decode($raw,true);$check(is_array($json),'Address JSON');preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$responses[$scenario]=$raw;return ['scenario'=>$scenario,'method'=>$method,'path'=>'/'.$module.'/store.Address/'.$action,'http_status'=>(int)($m[1]??0),'body'=>$json,'raw_json_shape'=>$shape(json_decode($raw))];};
$success=static function(array $case,string $message='')use($check):void{$check($case['http_status']===200&&($case['body']['status']??null)===200,'Address business success '.$case['scenario']);if($message!=='')$check($case['body']['message']===$message,'Address message');};
$ownRow=static function(string $module,array $form,?int $id=null,int $deleted=0)use($query,$tenants,$marker,$check,$project,$runtime,&$map,&$ids,&$proofs):array{$where=$id===null?'name=?':'address_id=?';$rows=$query('SELECT * FROM yoshop_store_address WHERE '.$where.' AND store_id=?',[$id===null?$form['name']:$id,$tenants[$module]]);$check(count($rows)===1&&strpos($rows[0]['name'],$marker.'_'.$module)===0,'Original owned address');$row=$rows[0];$actual=$project($row,false);
    foreach($form as $key=>$value){if($key==='cascader'){$check([$actual['province_id'],$actual['city_id'],$actual['region_id']]===$value,'Cascader integer original triplet');continue;}$check($actual[$key]===$value,'Address original input '.$key);}$check($actual['is_delete']===$deleted&&$actual['store_id']===$tenants[$module],'Address original store/delete');
    $rid=(int)$row['address_id'];$check($rid>0,'Generated positive address PK');$map['rows']['yoshop_store_address'][(string)$rid]=$tenants[$module];if(!isset($ids[$rid]))$ids[$rid]=count($ids)+1;file_put_contents($runtime.'/private-owned-map.json',json_encode($map));chmod($runtime.'/private-owned-map.json',0600);
    $value=$actual;$value['address_id']=$ids[$rid];$value['create_time']='VERIFIED_OWN_CREATE_TIME';$value['update_time']='VERIFIED_OWN_UPDATE_TIME';$proofs[]=['module'=>$module,'all_original_fields_verified'=>true,'owned_id_store_verified'=>true,'values'=>$value];return $row;};
$read=static function(string $module,string $name,string $action,array $args=[],?int $want=null)use($request,$success,$query,$tenants,$deep,$check,$project,&$ids,&$cases){$case=$request($module,$name,$action,$args);$success($case);$where='store_id=? AND is_delete=0';$values=[$tenants[$module]];
    if(!empty($args['type'])){$where.=' AND type=?';$values[]=$args['type'];}if(!empty($args['search'])){$where.=' AND (name LIKE ? OR phone LIKE ?)';$values[]='%'.$args['search'].'%';$values[]='%'.$args['search'].'%';}
    $rows=$query('SELECT * FROM yoshop_store_address WHERE '.$where.' ORDER BY sort,address_id',$values);$page=(int)($args['page']??1);$source=$action==='all'?$rows:array_slice($rows,($page-1)*15,15);if($want!==null)$check(count($source)===$want,'Address source cardinality');$expected=array_map($project,$source);$last=(int)ceil(count($rows)/15);$actualPage=$page>$last?($last?:1):$page;
    $expectedList=$action==='all'?$expected:['total'=>count($rows),'per_page'=>15,'current_page'=>$actualPage,'last_page'=>$last,'data'=>$expected];$deep($case['body']['data'],['list'=>$expectedList]);
    $out=&$case['body']['data']['list'];if($action==='list')$out=&$out['data'];foreach($out as &$r){$check(is_int($r['address_id'])&&isset($ids[$r['address_id']]),'Verified address integer mapping');$r['address_id']=$ids[$r['address_id']];$r['create_time']='VERIFIED_OWN_CREATE_TIME';$r['update_time']='VERIFIED_OWN_UPDATE_TIME';}unset($r,$out);$case['full_original_and_append_projection_equal']=true;$case['returned_rows']=count($source);$cases[]=$case;};
try{foreach($tenants as $module=>$tenant){$chains=$gate['readonly_region_chains'];$triplet=static function(array $r):array{return [$r['province_id'],$r['city_id'],$r['region_id']];};
    $a=['type'=>10,'name'=>$marker.'_'.$module.'_甲\'','phone'=>'00000000821','cascader'=>$triplet($chains[0]),'detail'=>"测试'一号\\楼",'sort'=>20];$b=['type'=>20,'name'=>$marker.'_'.$module.'_乙','phone'=>'00000000822','cascader'=>$triplet($chains[1]),'detail'=>'测试二号楼','sort'=>10];
    foreach([['A',$a],['B',$b]] as $item){$case=$request($module,'ADD_'.$item[0],'add',[],$item[1]);$cases[]=$case;$success($case,'添加成功');$row=$ownRow($module,$item[1]);if($item[0]==='A')$aid=(int)$row['address_id'];else $bid=(int)$row['address_id'];}
    $read($module,'LIST_NONEMPTY','list',[],2);$read($module,'ALL_NONEMPTY','all',[],2);$read($module,'TYPE_10','list',['type'=>10],1);$read($module,'TYPE_20','all',['type'=>20],1);$read($module,'PHONE_LEADING_ZERO_SEARCH','list',['search'=>'00000000821'],1);$read($module,'NAME_QUOTE_SEARCH','all',['search'=>"甲'"],1);
    $editA=$a;$editA['cascader']=$triplet($chains[2]);$editA['detail']="编辑'三号\\楼";$editA['sort']=10;$case=$request($module,'EDIT_REGION_SORT','edit',['addressId'=>$aid],$editA);$cases[]=$case;$success($case,'更新成功');$ownRow($module,$editA,$aid);$read($module,'ALL_SORT_TIE_AND_REGION_CHANGED','all',[],2);
    $zero=$b;$zero['cascader']=[0,0,0];$case=$request($module,'EDIT_ZERO_REGION','edit',['addressId'=>$bid],$zero);$cases[]=$case;$success($case,'更新成功');$ownRow($module,$zero,$bid);$read($module,'ALL_ZERO_OTHER_FALLBACK','all',[],2);
    $case=$request($module,'RESTORE_REAL_REGION','edit',['addressId'=>$bid],$b);$cases[]=$case;$success($case,'更新成功');$ownRow($module,$b,$bid);$read($module,'PAGE_TWO','list',['page'=>2],0);
    $pending[$module]=['aid'=>$aid,'bid'=>$bid,'a'=>$editA,'b'=>$b];
}
    // Both synthetic merchants are now nonempty; identical phone on another
    // tenant must not leak through the name|phone OR expression.
    foreach($tenants as $module=>$tenant)$read($module,'PHONE_WITH_OTHER_TENANT_ACTIVE','list',['search'=>'00000000821'],1);
    foreach($pending as $module=>$p){$case=$request($module,'DELETE_A','delete',['addressId'=>$p['aid']]);$cases[]=$case;$success($case,'删除成功');$ownRow($module,$p['a'],$p['aid'],1);$read($module,'ALL_ONE','all',[],1);
        $case=$request($module,'DELETE_B','delete',['addressId'=>$p['bid']]);$cases[]=$case;$success($case,'删除成功');$ownRow($module,$p['b'],$p['bid'],1);$read($module,'LIST_EMPTY','list',[],0);}
    $cached=$cache->get('region');$check(is_array($cached)&&($cached['version']??'')==='1.0.3','Actual private region cache populated');$deep($cached['all'],$region,'$private_region_cache.all');$count=['total'=>count($region),'province'=>0,'city'=>0,'region'=>0];foreach($region as $r){$kind=[1=>'province',2=>'city',3=>'region'][$r['level']]??null;$check($kind!==null,'Region legal level');$count[$kind]++;}$deep($cached['counts'],$count,'$private_region_cache.counts');$cacheProof=['original_all_rows'=>count($region),'full_original_projection_equal'=>true,'counts_equal'=>true,'version'=>'1.0.3','shared_cache_accessed'=>false];
}catch(Throwable $caught){$error=['class'=>get_class($caught),'message'=>$caught->getMessage(),'file'=>basename($caught->getFile()),'line'=>$caught->getLine()];}
$clean=$connect();$deleted=[];foreach($tenants as $module=>$tenant){$rows=$query('SELECT address_id,name FROM yoshop_store_address WHERE store_id=? AND name LIKE ?',[$tenant,$marker.'_'.$module.'%']);foreach($rows as $row){$check((int)$row['address_id']>0&&strpos($row['name'],$marker.'_'.$module)===0,'Exact cleanup address');$s=$clean->prepare('DELETE FROM yoshop_store_address WHERE address_id=? AND store_id=? AND name=?');$s->execute([$row['address_id'],$tenant,$row['name']]);$deleted[]=['owned_positive_id_store_exact_name'=>true];}}
foreach($tokens as $token)$cache->delete($token);$cache->clear();$fresh=$connect();$remaining=(int)$fresh->query('SELECT COUNT(*) FROM yoshop_store_address WHERE store_id IN (2147000821,2147000822)')->fetchColumn();$tokensAbsent=true;foreach($tokens as $token)$tokensAbsent=$tokensAbsent&&$cache->get($token)===null;
$cacheFiles=0;if(is_dir($runtime.'/cache/'))foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($runtime.'/cache/',FilesystemIterator::SKIP_DOTS)) as $f)if($f->isFile())$cacheFiles++;
$cleanup=['fresh_pdo_owned_rows'=>$remaining,'tokens_absent'=>$tokensAbsent,'private_region_cache_absent'=>$cache->get('region')===null,'private_cache_files'=>$cacheFiles,'address_and_region_full_sha_unchanged'=>$snapshot()===$before,'exact_delete_scopes'=>$deleted,'same_named_user_address_primary_keys_not_treated_as_references'=>true];
foreach($cases as &$case){$trace=$case['body']['trace_id']??null;$case['trace_format_valid']=is_string($trace)&&preg_match('/^[0-9a-f]{13}$/D',$trace)===1;if($case['trace_format_valid'])$case['body']['trace_id']='VALID_DYNAMIC_TRACE';}unset($case);
$events=[];foreach(file($runtime.'/queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$events[]=json_decode($line,true);$audit=[];foreach(file($runtime.'/write-audit.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$audit[]=json_decode($line,true);
file_put_contents($runtime.'/private-responses.json',json_encode($responses,JSON_UNESCAPED_UNICODE));file_put_contents($runtime.'/private-id-map.json',json_encode($ids));
$report=['lane'=>$lane,'cases'=>$cases,'original_proofs'=>$proofs,'loaded_boundary'=>$boundary,'region_cache_proof'=>$cacheProof,'natural_before'=>$before,'write_audit'=>$audit,'cleanup'=>$cleanup,'error'=>$error,'events'=>$events];file_put_contents($runtime.'/report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
echo json_encode(['lane'=>$lane,'http_cases'=>count($cases),'original_proofs'=>count($proofs),'cache_proof'=>$cacheProof,'cleanup'=>$cleanup,'error'=>$error,'report'=>$runtime.'/report.json']),"\n";exit($error!==null||$remaining!==0||!$tokensAbsent||$cacheFiles!==0||!$cleanup['address_and_region_full_sha_unchanged']||in_array(false,array_column($audit,'owned_scope_verified'),true)?1:0);
