Files
Investidor-V2/tools/portal-country-https-active-preflight.php
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

80 lines
7.5 KiB
PHP

<?php
// Read-only active preflight of the EXACT authorized replacement delivery. No providers/sessions.
use Illuminate\Foundation\Bootstrap\LoadConfiguration;
use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
use Illuminate\Encryption\Encrypter;
$root = dirname(__DIR__);
require $root.'/vendor/autoload.php';
ini_set('zend.exception_ignore_args', '1');
set_exception_handler(static function (Throwable $e): void {
fwrite(STDERR, 'HTTPS activation preflight stopped: '.$e->getMessage().PHP_EOL); exit(1);
});
$read = static fn ($p) => json_decode(file_get_contents($p), true, 512, JSON_THROW_ON_ERROR);
$private = $root.'/storage/app/private/investmoz-v2/portal-audit';
$out = $root.'/outputs/investmoz-country02-https-20261011';
$manifest = $read($out.'/manifesto-candidata.json');
$sealed = $read($out.'/manifesto-pacote.json');
throw_unless($manifest['code_hash'] === '162daff6ce08117582dd7d796639d02b156d1b940cd85ef3587ff88a65747edd'
&& $manifest['delivery_revision'] === '13644ed74a48b69767ae3f0759a1adb430b7ba982169d2aaf409363429276709', RuntimeException::class, 'Authorized replacement differs.');
throw_unless(hash_file('sha256', $out.'/manifesto-candidata.json') === $sealed['manifesto_candidata_sha256']
&& hash_file('sha256', $out.'/'.$sealed['archive']) === $sealed['archive_sha256'], RuntimeException::class, 'Sealed package differs.');
$proof = $read($read($private.'/country-https-backup-latest.json')['path']);
throw_unless($proof['code_hash'] === $manifest['code_hash'] && $proof['production_manifest_sha256'] === $manifest['production']['manifest_sha256']
&& strtotime($proof['verified_at']) >= time()-86400, RuntimeException::class, 'Corresponding backup expired or differs.');
foreach (['restore_verified','files_verified','sequences_verified','compatibility_verified','reversal_verified','qa_passed'] as $key) {
throw_unless($proof[$key] === true, RuntimeException::class, 'Incomplete restore/QA evidence.');
}
throw_unless(hash_file('sha256', $proof['dump_path']) === $proof['dump_sha256']
&& hash_file('sha256', $proof['files_path']) === $proof['files_sha256'], RuntimeException::class, 'Backup bytes differ.');
$app = require $root.'/bootstrap/app.php';
(new LoadEnvironmentVariables)->bootstrap($app); (new LoadConfiguration)->bootstrap($app);
$c = config('database.connections.'.config('database.default'));
throw_unless($c['driver'] === 'pgsql' && empty($c['url']) && (int)$c['port'] === 5432 && $c['database'] === 'investemoz', RuntimeException::class, 'Exact active target required.');
throw_unless(!config('portal.organization_country_enabled') && !$app->configurationIsCached(), RuntimeException::class, 'Disabled flag/uncached baseline required.');
$zip = new ZipArchive;
throw_unless($zip->open($proof['files_path']) === true, RuntimeException::class, 'Restore archive unreadable.');
$archived = json_decode($zip->getFromName('runtime/effective-runtime.json'), true, 512, JSON_THROW_ON_ERROR);
throw_unless($archived['app_key'] === config('app.key') && $archived['cipher'] === config('app.cipher')
&& $zip->getFromName('runtime/.env') === file_get_contents($root.'/.env'), RuntimeException::class, 'Stable key/environment differs.');
$zip->close();
$key = config('app.key'); $key = str_starts_with($key, 'base64:') ? base64_decode(substr($key, 7), true) : $key;
$crypt = new Encrypter($key, config('app.cipher'));
$pdo = new PDO('pgsql:host='.$c['host'].';port=5432;dbname=investemoz', $c['username'], $c['password'], [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$pdo->exec('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
$identity = $pdo->query("SELECT current_database() AS database,inet_server_port() AS port,current_setting('transaction_read_only') AS read_only")->fetch(PDO::FETCH_ASSOC);
throw_unless($identity['database'] === 'investemoz' && (int)$identity['port'] === 5432 && $identity['read_only'] === 'on', RuntimeException::class, 'Actual target differs.');
$tables=[]; $changed=[];
foreach ($pdo->query("SELECT schemaname,tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,tablename")->fetchAll(PDO::FETCH_ASSOC) as $t) {
$name='"'.str_replace('"','""',$t['schemaname']).'"."'.str_replace('"','""',$t['tablename']).'"';
$rows=$pdo->query('SELECT row_to_json(t)::text FROM '.$name.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN);
$tables[$name]=['count'=>count($rows),'sha256'=>hash('sha256',implode("\n",$rows))];
if (($proof['tables'][$name] ?? null) !== $tables[$name]) $changed[]=$name;
}
throw_unless(array_keys($tables) === array_keys($proof['tables']), RuntimeException::class, 'Table inventory differs.');
if ($changed !== []) {
file_put_contents($private.'/country-https-active-backup-renew-needed.json',json_encode([
'at'=>gmdate('c'),'changed_tables'=>$changed,'changed_table_counts'=>array_intersect_key($tables,array_flip($changed)),
'active_SQL_writes'=>0,'action'=>'Renew snapshot/restore before staging'],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
throw new RuntimeException('Rows changed since verified snapshot; renew backup before activation.');
}
$sequences=$pdo->query("SELECT * FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC);
throw_unless($sequences === $proof['sequences'], RuntimeException::class, 'Sequence values differ.');
$counts=[];
foreach (['users','organizations','memberships','migrations','audit_events','sessions'] as $table) $counts[$table]=(int)$pdo->query('SELECT count(*) FROM "'.$table.'"')->fetchColumn();
$mfaCount=0;
foreach ($pdo->query('SELECT two_factor_secret FROM users WHERE two_factor_secret IS NOT NULL')->fetchAll(PDO::FETCH_COLUMN) as $ciphertext) {
$plain=$crypt->decrypt($ciphertext); throw_unless(is_string($plain) && strlen($plain)>0, RuntimeException::class, 'Original MFA envelope cannot be decrypted.'); unset($plain); $mfaCount++;
}
$migrations=$pdo->query("SELECT migration FROM migrations WHERE migration IN ('2026_10_09_030000_extend_opportunity_resources','2026_10_10_040000_create_portal_profile_authorization','2026_10_09_190000_prepare_investor_path','2026_10_10_050000_extend_organization_country') ORDER BY migration")->fetchAll(PDO::FETCH_COLUMN);
$columns=$pdo->query("SELECT column_name FROM information_schema.columns WHERE table_schema='public' AND table_name='organizations' AND column_name IN ('incorporation_country_code','incorporation_country_provenance') ORDER BY column_name")->fetchAll(PDO::FETCH_COLUMN);
throw_unless($migrations === [] && $columns === [], RuntimeException::class, 'Installed resources differ; stop instead of reinstalling.');
$pdo->exec('ROLLBACK');
$result=['result'=>'passed','at'=>gmdate('c'),'code_hash'=>$manifest['code_hash'],'delivery_revision'=>$manifest['delivery_revision'],'target'=>$identity,
'counts'=>$counts,'tables'=>$tables,'sequences_verified'=>true,'backup_verified_at'=>$proof['verified_at'],'backup_age_seconds'=>time()-strtotime($proof['verified_at']),
'backup_restore_and_actual_rows_correspond'=>true,'stable_key_match'=>true,'original_mfa_envelopes_recovered'=>$mfaCount,
'country_flag_enabled'=>false,'active_database_writes'=>0,'installed_packages'=>$migrations,'country_columns'=>$columns,
'source_code_hash'=>$proof['source_code_hash'],'readiness_proof_path'=>$read($private.'/country-https-backup-latest.json')['path']];
file_put_contents($private.'/country-https-active-preflight-20261011.json',json_encode($result,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
echo json_encode(array_diff_key($result,['tables'=>true,'readiness_proof_path'=>true]),JSON_THROW_ON_ERROR).PHP_EOL;