Files
compras/scripts/sgf-read-8039.php
2026-11-02 20:27:11 +02:00

76 lines
6.9 KiB
PHP

<?php
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\DB;
use Symfony\Component\Process\Process;
use Tempo\Purchasing\Delivery\CodeDeployment;
$root=dirname(__DIR__);
$host='C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/development-app';
$recovery=dirname($host).'/private/read-correction-recovery';
$candidate=json_decode(file_get_contents($root.'/docs/proofs/sgf-read-candidate.json'),true,32,JSON_THROW_ON_ERROR);
require $host.'/vendor/autoload.php';
$app=require $host.'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$db=DB::connection();
$identity=$db->selectOne("select current_database() as db,current_user as role,inet_server_port() as port,current_setting('cluster_name') as cluster");
if(str_replace('\\','/',$app->basePath())!==$host||$identity->db!=='sgf_modules_dev_2576427d1939'||$identity->role!=='sgf_modules_qa_2576427d1939'||(int)$identity->port!==55439||$identity->cluster!=='sgf_modular_regression_2576427d1939'||config('app.url')!=='http://127.0.0.1:8039')throw new RuntimeException('Wrong demonstration');
$record=$db->table('ir_requests')->where(['id'=>1,'tenant_id'=>1,'company_id'=>1,'unit_id'=>1])->first();
if(!$record||$record->status!=='approved'||$record->total_minor!==10000||$record->version!==3||$db->table('cb_budget_entries')->where('kind','reserve')->count()!==1)throw new RuntimeException('Wrong existing request');
require_once $root.'/integrations/sgf/operational/CodeDeployment.php';
$deploy=new CodeDeployment($host);
$fingerprints=function()use($db){
return $db->transaction(function()use($db){
$db->statement('SET TRANSACTION READ ONLY');$hashes=[];
foreach($db->select("select tablename from pg_tables where schemaname='public' order by tablename") as $t){
$rows=$db->select('select to_jsonb(t)::text as data from "'.$t->tablename.'" t order by to_jsonb(t)::text');
$hashes[$t->tablename]=hash('sha256',json_encode(array_column($rows,'data'),JSON_THROW_ON_ERROR));
}return $hashes;
});
};
$action=$argv[1]??'';
if($action==='prepare'){
if(is_dir($recovery))throw new RuntimeException('Recovery exists; no overwrite');
$plan=$deploy->plan($candidate['directory'],$candidate['archive']['manifest_sha256']);
mkdir($recovery,0700,true);
$cfg=$db->getConfig();
(new Process(['C:/Program Files/PostgreSQL/17/bin/pg_dump.exe','--format=custom','--file='.$recovery.'/before.dump','--dbname='.$identity->db],null,['PGHOST'=>'127.0.0.1','PGPORT'=>'55439','PGUSER'=>$identity->role,'PGPASSWORD'=>$cfg['password']],null,90))->mustRun();
(new Process(['C:/Program Files/PostgreSQL/17/bin/pg_restore.exe','--list',$recovery.'/before.dump']))->mustRun();
$baseline=['identity'=>$identity,'plan'=>$plan,'data_fingerprints'=>$fingerprints(),'backup_sha256'=>hash_file('sha256',$recovery.'/before.dump'),'pointer_sha256'=>hash_file('sha256',$host.'/packages/purchasing/current.json'),'config_hashes'=>[]];
foreach(['.env','config/modules.php','config/purchasing-operational.php','config/purchasing-internal.php','config/purchasing-access.php'] as $file)$baseline['config_hashes'][$file]=hash_file('sha256',$host.'/'.$file);
file_put_contents($recovery.'/baseline.json',json_encode($baseline,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
echo json_encode(['prepared'=>true,'tables'=>count($baseline['data_fingerprints']),'backup_sha256'=>$baseline['backup_sha256'],'plan'=>$plan],JSON_PRETTY_PRINT);
}else{
$baseline=json_decode(file_get_contents($recovery.'/baseline.json'),true,64,JSON_THROW_ON_ERROR);
$currentFingerprints=$fingerprints();
$changedTables=array_keys(array_filter($currentFingerprints,fn($hash,$table)=>$hash!==($baseline['data_fingerprints'][$table]??null),ARRAY_FILTER_USE_BOTH));
$auditAppended=false;
if(in_array('identity_audit',$changedTables,true)){
$extract=new Process(['C:/Program Files/PostgreSQL/17/bin/pg_restore.exe','--data-only','--table=identity_audit','--file=-',$recovery.'/before.dump']);$extract->mustRun();
if(!preg_match('~COPY public\.identity_audit \(id,[^)]*\) FROM stdin;\r?\n(.*?)\r?\n\\\\\.~s',$extract->getOutput(),$match))throw new RuntimeException('Audit baseline cannot be reconciled');
$ids=array_map(fn($line)=>(int)explode("\t",$line)[0],preg_split('/\r?\n/',$match[1]));
$rows=$db->select('select to_jsonb(t)::text as data from identity_audit t where id<=? order by to_jsonb(t)::text',[max($ids)]);
if(hash('sha256',json_encode(array_column($rows,'data'),JSON_THROW_ON_ERROR))!==$baseline['data_fingerprints']['identity_audit'])throw new RuntimeException('Existing audit modified');
$auditAppended=true;$changedTables=array_values(array_diff($changedTables,['identity_audit']));
}
if($changedTables!==[])throw new RuntimeException('Database rows changed: '.implode(', ',$changedTables));
if(hash_file('sha256',$host.'/packages/purchasing/current.json')!==$baseline['pointer_sha256'])throw new RuntimeException('Pointer changed');
foreach($baseline['config_hashes'] as $file=>$hash)if(hash_file('sha256',$host.'/'.$file)!==$hash)throw new RuntimeException('Configuration changed');
if($action==='verify'){
$report=['passed'=>true,'identity'=>$identity,'data_tables_reconciled'=>count($baseline['data_fingerprints']),'audit_appended_only'=>$auditAppended,'request_id'=>1,'request_version'=>3,'amount_minor'=>10000,'reservation_count'=>1,'rights_and_accounts_unchanged'=>true,'config_unchanged'=>true,'pointer_unchanged'=>true,'candidate_manifest_sha256'=>hash_file('sha256',$host.'/packages/internal-requisitions/manifest.json'),'backup_sha256'=>$baseline['backup_sha256'],'checked_at'=>gmdate('c')];
file_put_contents($root.'/docs/proofs/sgf-read-8039-data.json',json_encode($report,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo json_encode($report,JSON_PRETTY_PRINT);
}else{
if(is_file($host.'/storage/framework/down'))throw new RuntimeException('Unexpected maintenance');
file_put_contents($host.'/storage/framework/down',json_encode(['time'=>time(),'retry'=>60]));
if($action==='rollback'){
$applied=json_decode(file_get_contents($recovery.'/applied.json'),true);
$result=$deploy->rollback($applied['deployment_id'],'Authorised read fix recovery proof 8039',true);
file_put_contents($recovery.'/rollback.json',json_encode($result,JSON_PRETTY_PRINT));
}elseif(in_array($action,['apply','reapply'],true)){
$plan=$deploy->plan($candidate['directory'],$candidate['archive']['manifest_sha256']);
$result=$deploy->apply($candidate['directory'],$candidate['archive']['manifest_sha256'],$plan['target_sha256'],'Authorised read-only visibility correction 8039',true);
file_put_contents($recovery.'/'.($action==='apply'?'applied':'reapplied').'.json',json_encode($result,JSON_PRETTY_PRINT));
}else throw new RuntimeException('Use prepare/apply/rollback/reapply/verify');
unlink($host.'/storage/framework/down');echo json_encode($result,JSON_PRETTY_PRINT);
}
}