make(Kernel::class)->bootstrap(); $active = DB::connection(); if ($active->getDatabaseName() !== 'investemoz' || (string) $active->getConfig('port') !== '5432') { throw new RuntimeException('Unexpected active target.'); } config(['database.connections.anchor_restore' => [...config('database.connections.pgsql'), 'host' => '127.0.0.1', 'port' => '55441', 'database' => 'anchor_restore_20261007_test', 'username' => 'postgres', 'password' => '', 'url' => null]]); $restore = DB::connection('anchor_restore'); $identity = $restore->selectOne('select current_database() as database, inet_server_port() as port'); if ($identity->database !== 'anchor_restore_20261007_test' || (int) $identity->port !== 55441) { throw new RuntimeException('Restore verification requires isolated QA.'); } $report = $active->transaction(function () use ($active, $restore) { $active->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY'); $counts = []; foreach ($active->select("select tablename from pg_tables where schemaname='public' order by tablename") as $table) { $name = $table->tablename; $a = $active->table($name)->count(); $b = $restore->table($name)->count(); if ($a !== $b) { throw new RuntimeException('Restore count differs for '.$name); } $counts[$name] = $a; } $hashes = []; foreach (['projects', 'economic_clusters', 'investment_dossiers', 'project_locations', 'cluster_projects', 'opportunity_projects', 'opportunity_clusters', 'value_chains', 'value_chain_links', 'opportunities', 'geo_locations', 'geo_geometries'] as $table) { $hash = function ($db) use ($table) { $rows = array_map(fn ($row) => $row->value, $db->select('select row_to_json(t)::text as value from public.'.$table.' as t')); sort($rows, SORT_STRING); return hash('sha256', implode("\n", $rows)); }; $a = $hash($active); if ($a !== $hash($restore)) { throw new RuntimeException('Restore data differs for '.$table); } $hashes[$table] = $a; } return ['table_counts' => $counts, 'relation_table_hashes' => $hashes]; }); $directory = storage_path('app/private/anchor-project-reconciliation'); $dump = $directory.'/backup-20261007/database.dump'; $proof = [...$report, 'restore_verified' => true, 'verified_at' => date(DATE_ATOM), 'target' => ['database' => 'investemoz', 'port' => 5432], 'restored_target' => ['database' => $identity->database, 'port' => $identity->port], 'dump_path' => $dump, 'sha256' => hash_file('sha256', $dump)]; file_put_contents($directory.'/backup-proof.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); echo 'Restore verified: '.count($report['table_counts']).' table counts and '.count($report['relation_table_hashes']).' content hashes.'.PHP_EOL;