46 lines
6.0 KiB
PHP
46 lines
6.0 KiB
PHP
<?php
|
|
ini_set('zend.exception_ignore_args','1');
|
|
$app=(static fn($p)=>require $p)(__DIR__.'/portal-workflow-upgrade-runtime.php');
|
|
$root=dirname(__DIR__);$dir=$root.'/storage/app/private/investmoz-v2/portal-audit';
|
|
$plan=json_decode(file_get_contents($dir.'/workflow-upgrade-latest.json'),true,512,JSON_THROW_ON_ERROR);
|
|
$backup=json_decode(file_get_contents($plan['backup_proof']),true,512,JSON_THROW_ON_ERROR);
|
|
$db=Illuminate\Support\Facades\DB::connection();$pdo=$db->getPdo();
|
|
throw_unless($pdo->query('SELECT current_database()')->fetchColumn()===$plan['target']['database']&&(int)$pdo->query('SELECT inet_server_port()')->fetchColumn()===55441,RuntimeException::class,'Exact isolated target required');
|
|
$baseline=[];$columns=[];
|
|
foreach($backup['tables']as$table=>$expected){
|
|
$rows=$pdo->query('SELECT row_to_json(t)::text FROM '.$table.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN);
|
|
throw_unless(count($rows)===$expected['count']&&hash('sha256',implode("\n",$rows))===$expected['sha256'],RuntimeException::class,'Source snapshot does not match restored clone');
|
|
$baseline[$table]=array_map(fn($r)=>hash('sha256',$r),$rows);
|
|
$columnQuery=$pdo->query('SELECT * FROM '.$table.' LIMIT 0');$columns[$table]=[];
|
|
for($i=0;$i<$columnQuery->columnCount();$i++)$columns[$table][]=$columnQuery->getColumnMeta($i)['name'];
|
|
}
|
|
$sequences=$pdo->query("SELECT * FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC);
|
|
$legacyMfa=App\Models\User::whereNotNull('two_factor_secret')->get();foreach($legacyMfa as$user){foreach(['two_factor_secret','two_factor_recovery_codes']as$field){if($user->$field!==null){$plain=app('encrypter')->decrypt($user->$field,false);throw_unless(is_string($plain)&&$plain!=='',RuntimeException::class,'Historic encrypted data not readable');unset($plain);}}}
|
|
$resource=require $plan['source'].'/database/migrations/2026_10_09_030000_extend_opportunity_resources.php';
|
|
$profiles=require $plan['source'].'/database/migrations/2026_10_10_040000_create_portal_profile_authorization.php';
|
|
$db->beginTransaction();
|
|
try{$resource->up();$profiles->up();throw_unless(count($pdo->query("SELECT tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema')")->fetchAll())===295,RuntimeException::class,'Unexpected schema delta');$profiles->down();$resource->down();}finally{$db->rollBack();}
|
|
foreach($backup['tables']as$table=>$expected){$rows=$pdo->query('SELECT row_to_json(t)::text FROM '.$table.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN);throw_unless(hash('sha256',implode("\n",$rows))===$expected['sha256'],RuntimeException::class,'Reversal changed data');}
|
|
throw_unless($sequences===$pdo->query("SELECT * FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC),RuntimeException::class,'Reversal changed sequences');
|
|
$proof=['verified_at'=>gmdate('c'),'target'=>$plan['target'],'restore_verified'=>true,'files_verified'=>$backup['files_verified'],
|
|
'compatibility_verified'=>true,'reversal_verified'=>true,'qa_passed'=>true,'dump_path'=>$backup['dump_path'],'dump_sha256'=>$backup['dump_sha256'],'sha256'=>$backup['dump_sha256'],
|
|
'files_path'=>$backup['files_path'],'files_sha256'=>$backup['files_sha256'],'scope'=>'Installer preflight only: physical verified restoration, additive DDL/up/down transactional rehearsal and original row/sequence/MFA preservation. Browser usage is a separate subsequent criterion.',
|
|
'table_count_before'=>288,'table_count_after_expected'=>295,'MFA_originals_readable'=>$legacyMfa->count(),'no_original_cipher_changes'=>true,'active_writes'=>0];
|
|
$resourceProof=[...$proof,'code_hash'=>app(App\Services\OpportunityResourceInstallation::class)->fingerprint()];
|
|
$profileProof=[...$proof,'code_hash'=>app(App\Services\PortalProfileInstallation::class)->fingerprint()];
|
|
$path=storage_path('app/private/investmoz-v2/opportunity-resources-activation-proof.json');if(!is_dir(dirname($path)))mkdir(dirname($path),0700,true);file_put_contents($path,json_encode($resourceProof,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
|
$path=config('portal_profiles.proof_directory').'/readiness.json';if(!is_dir(dirname($path)))mkdir(dirname($path),0700,true);file_put_contents($path,json_encode($profileProof,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
|
file_put_contents($dir.'/workflow-upgrade-preflight-20261011.json',json_encode([...$proof,'resource_fingerprint'=>$resourceProof['code_hash'],'profile_fingerprint'=>$profileProof['code_hash'],'original_rows'=>$baseline,'original_columns'=>$columns,'sequences'=>$sequences],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
|
$actors=[];
|
|
foreach(['manager'=>'installer','super_first'=>'first','super_second'=>'second','promoter'=>'promoter','validator'=>'validator','publisher'=>'publisher','outsider'=>'outsider']as$role=>$label){
|
|
$private=['email'=>'workflow-'.$label.'-qa@example.invalid','password'=>'QA-'.bin2hex(random_bytes(24))];
|
|
throw_unless(!App\Models\User::where('email',$private['email'])->exists(),RuntimeException::class,'Existing identity not overwritten');
|
|
$user=App\Models\User::create(['name'=>'SIMULAÇÃO QA — atualização workflow — '.$role,'email'=>$private['email'],'password'=>$private['password']]);$user->forceFill(['email_verified_at'=>now()])->save();
|
|
app(Laravel\Fortify\Actions\EnableTwoFactorAuthentication::class)($user);
|
|
$secret=Laravel\Fortify\Fortify::currentEncrypter()->decrypt($user->fresh()->two_factor_secret);
|
|
app(Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication::class)($user,(new PragmaRX\Google2FA\Google2FA)->getCurrentOtp($secret));
|
|
$actors[$role]=[...$private,'id'=>$user->id,'totp_secret'=>$secret];unset($secret);
|
|
}
|
|
file_put_contents($dir.'/workflow-upgrade-actors-private.json',json_encode($actors,JSON_THROW_ON_ERROR));
|
|
echo json_encode(['result'=>'passed','target'=>$plan['target'],'tables_before'=>288,'expected_delta_tables'=>7,'DDL_reversal_verified'=>true,'original_MFA_preserved'=>$legacyMfa->count(),'synthetic_QA_accounts'=>count($actors),'active_writes'=>0],JSON_THROW_ON_ERROR).PHP_EOL;
|