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); } }