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

107 lines
7.7 KiB
PHP

<?php
use Dotenv\Dotenv;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\DB;
use Symfony\Component\Process\Process;
require dirname(__DIR__).'/vendor/autoload.php';
$app = require dirname(__DIR__).'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$root = dirname(__DIR__);
$host = realpath($root.'/../sgf-tempo');
$config = DB::connection('pgsql')->getConfig();
if ($config['host'] !== '127.0.0.1') {
throw new RuntimeException('Local isolated cluster required.');
}
config(['database.connections.fixed_admin' => [...$config, 'url' => null, 'database' => 'postgres']]);
$admin = DB::connection('fixed_admin');
$suffix = bin2hex(random_bytes(6));
$owner = 'fix_owner_'.$suffix;
$runtime = 'fix_app_'.$suffix;
$database = 'sgf_reproduction_'.$suffix;
$ownerPassword = bin2hex(random_bytes(32));
$runtimePassword = bin2hex(random_bytes(32));
$roles = [];
$created = false;
$restoreCreated = false;
$restoreDatabase = 'sgf_reproduction_restore_'.$suffix;
$exit = 1;
$work = sys_get_temp_dir().'/sgf-corrected-'.$suffix;
mkdir($work, 0700);
$pointer = file_get_contents($host.'/packages/purchasing/current.json');
try {
$sourceSettings = Dotenv::parse(file_get_contents($host.'/.env'));
$sourceEnvironment = array_intersect_key($sourceSettings, array_flip(['DB_CONNECTION', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD', 'APP_ENV']));
$sourceEnvironment += ['APP_BASE_PATH' => $host, 'DB_URL' => $sourceSettings['DB_URL'] ?? '', 'APP_CONFIG_CACHE' => $host.'/bootstrap/cache/config.php'];
$export = new Process(['powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $host.'/scripts/sgf.ps1', 'php', $root.'/tests/Support/sgf-corrected-export.php'], $host, [...$sourceEnvironment, 'FIX_SOURCE_HOST' => $host, 'FIX_SCHEMA_FILE' => $work.'/source-schema.dump'], null, 90);
$export->mustRun();
$inventory = json_decode($export->getOutput(), true, 64, JSON_THROW_ON_ERROR);
if ($inventory['database'] !== $sourceSettings['DB_DATABASE']) {
throw new RuntimeException('Wrong reproduction source.');
}
file_put_contents($work.'/baseline.json', json_encode($inventory, JSON_THROW_ON_ERROR));
$prepare = new Process([PHP_BINARY, $root.'/scripts/prepare-sgf-operational.php', $host], $root);
$prepare->mustRun();
$artifact = json_decode($prepare->getOutput(), true, 16, JSON_THROW_ON_ERROR);
require $root.'/tests/Support/sgf-update-fixture.php';
$manifest = json_decode(file_get_contents($artifact['directory'].'/manifest.json'), true, 32, JSON_THROW_ON_ERROR);
$clone = updateFixture($host, $manifest, true);
foreach ([$owner => $ownerPassword, $runtime => $runtimePassword] as $role => $password) {
$admin->statement('CREATE ROLE "'.$role.'" LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS PASSWORD '.$admin->getPdo()->quote($password));
$roles[] = $role;
}
$admin->statement('CREATE DATABASE "'.$database.'" OWNER "'.$owner.'"');
$created = true;
$env = ['APP_ENV' => 'production', 'APP_DEBUG' => 'false', 'APP_BASE_PATH' => $clone, 'APP_KEY' => 'base64:'.base64_encode(random_bytes(32)), 'DB_CONNECTION' => 'pgsql', 'DB_URL' => '', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => (string) $config['port'], 'DB_DATABASE' => $database, 'DB_USERNAME' => $owner, 'DB_PASSWORD' => $ownerPassword, 'SESSION_DRIVER' => 'file', 'SESSION_FILES' => $clone.'/storage/framework/sessions', 'SESSION_COOKIE' => 'corrected_'.$suffix, 'CACHE_STORE' => 'array', 'QUEUE_CONNECTION' => 'sync',
'FIX_SOURCE_HOST' => $host, 'FIX_CLONE' => $clone, 'FIX_BUNDLE' => $artifact['directory'], 'FIX_ROOT' => $root, 'FIX_WORK' => $work, 'FIX_RUNTIME' => $runtime, 'FIX_RUNTIME_PASSWORD' => $runtimePassword];
$restore = new Process(['C:/Program Files/PostgreSQL/17/bin/pg_restore.exe', '--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$database, $work.'/source-schema.dump'], null, ['PGHOST' => '127.0.0.1', 'PGPORT' => (string) $config['port'], 'PGUSER' => $owner, 'PGPASSWORD' => $ownerPassword], null, 60);
$restore->mustRun();
$proofProcess = new Process(['powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $host.'/scripts/sgf.ps1', 'php', $root.'/tests/Support/sgf-corrected-proof.php'], $host, $env, null, 180);
$proofProcess->mustRun();
try {
$proof = json_decode($proofProcess->getOutput(), true, 64, JSON_THROW_ON_ERROR);
} catch (JsonException) {
file_put_contents($work.'/failure.txt', $proofProcess->getOutput().$proofProcess->getErrorOutput());
throw new RuntimeException('Private diagnostic '.$work.'/failure.txt');
}
file_put_contents($work.'/expected.json', json_encode($proof['data_fingerprints'], JSON_THROW_ON_ERROR));
$admin->statement('CREATE DATABASE "'.$restoreDatabase.'" OWNER "'.$owner.'"');
$restoreCreated = true;
$restoreData = new Process(['C:/Program Files/PostgreSQL/17/bin/pg_restore.exe', '--no-owner', '--no-privileges', '--exit-on-error', '--dbname='.$restoreDatabase, $work.'/installed.dump'], null, ['PGHOST' => '127.0.0.1', 'PGPORT' => (string) $config['port'], 'PGUSER' => $owner, 'PGPASSWORD' => $ownerPassword], null, 90);
$restoreData->mustRun();
$readRestore = new Process(['powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $host.'/scripts/sgf.ps1', 'php', $root.'/tests/Support/sgf-corrected-restored.php'], $host, [...$env, 'DB_DATABASE' => $restoreDatabase], null, 60);
$readRestore->mustRun();
$proof['data_recovery'] = json_decode($readRestore->getOutput(), true, 32, JSON_THROW_ON_ERROR);
$after = new Process(['powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $host.'/scripts/sgf.ps1', 'php', $root.'/tests/Support/sgf-corrected-export.php'], $host, [...$sourceEnvironment, 'FIX_SOURCE_HOST' => $host, 'FIX_CHECK_ONLY' => '1'], null, 60);
$after->mustRun();
$afterState = json_decode($after->getOutput(), true, 64, JSON_THROW_ON_ERROR);
if ($afterState['source_security_fingerprints'] !== $inventory['source_security_fingerprints'] || $afterState['tables'] !== $inventory['tables']) {
throw new RuntimeException('Destination changed during isolated proof; reconcile.');
}
$proof['destination_security_and_schema_preserved'] = true;
if (! $proof['passed'] || file_get_contents($host.'/packages/purchasing/current.json') !== $pointer) {
throw new RuntimeException('Proof or destination preservation failed.');
}
$proof += ['artifact' => $artifact, 'source_schema_tables' => count($inventory['tables']), 'source_rows_exported' => 0, 'source_schema_sha256' => $inventory['schema_sha256'], 'runtime_and_migrator_separate' => true, 'destination_version' => '0.1.1', 'destination_changed' => false, 'prior_proofs_reused' => true, 'checked_at' => gmdate('c')];
$exit = 0;
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage()."\n");
} finally {
if ($restoreCreated) {
$admin->select('select pg_terminate_backend(pid) from pg_stat_activity where datname=? and pid<>pg_backend_pid()', [$restoreDatabase]);
$admin->statement('DROP DATABASE "'.$restoreDatabase.'"');
}
if ($created) {
$admin->select('select pg_terminate_backend(pid) from pg_stat_activity where datname=? and pid<>pg_backend_pid()', [$database]);
$admin->statement('DROP DATABASE "'.$database.'"');
}foreach (array_reverse($roles) as $role) {
$admin->statement('DROP ROLE "'.$role.'"');
}
}
if ($exit === 0) {
$proof['test_database_and_roles_removed'] = true;
file_put_contents($root.'/docs/proofs/sgf-corrected-delivery.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
echo json_encode($proof,JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)."\n";
}exit($exit);