Files
Investidor-V2/tools/territorial-activation-reversal.php

80 lines
5.6 KiB
PHP

<?php
use App\Services\InvestmentTerritorialTraceability;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\DB;
// Only the restored, isolated backup can be mutated by this compatibility rehearsal.
$root = dirname(__DIR__);
$directory = $root.'/storage/app/private/investmoz-v2';
$proofFile = $directory.'/territory-backup-proof.json';
$proof = json_decode(file_get_contents($proofFile), true, 512, JSON_THROW_ON_ERROR);
$target = $proof['restored_target'];
if (! preg_match('/^desk_restore_\d{8}_\d{6}_test$/', $target['database']) || (int) $target['port'] !== 55441 || ! $proof['restore_verified']) {
throw new RuntimeException('Verified isolated restoration required.');
}
foreach (['APP_ENV' => 'testing', 'APP_KEY' => 'base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', 'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441', 'DB_DATABASE' => $target['database'], 'DB_USERNAME' => 'postgres', 'DB_PASSWORD' => '', 'DB_URL' => '', 'CACHE_STORE' => 'array', 'QUEUE_CONNECTION' => 'sync'] as $key => $value) {
putenv($key.'='.$value);
$_ENV[$key] = $_SERVER[$key] = $value;
}
require $root.'/vendor/autoload.php';
$app = require $root.'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$connection = config('database.connections.pgsql');
if (config('database.default') !== 'pgsql' || $connection['host'] !== '127.0.0.1' || (int) $connection['port'] !== 55441 || $connection['database'] !== $target['database'] || ! empty($connection['url'])) {
throw new RuntimeException('Refusing non-QA configuration.');
}
$identity = DB::selectOne('SELECT current_database() AS database, inet_server_port() AS port');
if ($identity->database !== $target['database'] || (int) $identity->port !== 55441) {
throw new RuntimeException('Refusing non-QA connection.');
}
$qaDirectory = $directory.'/qa-traceability-reversal';
if (! is_dir($qaDirectory)) {
mkdir($qaDirectory, 0700, true);
}
$activeBytes = file_get_contents($directory.'/territory-activation-plan.json');
$activePlan = json_decode($activeBytes, true, 512, JSON_THROW_ON_ERROR);
$plan = $activePlan;
$plan['target'] = $target;
$qaBytes = json_encode($plan, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
$qaHash = hash('sha256', $qaBytes);
file_put_contents($qaDirectory.'/territory-activation-plan.json', $qaBytes);
// This fixture authorizes the QA rehearsal only; it is not evidence of a completed reversal.
$qaProof = [...$proof, 'target' => $target, 'reversal_verified' => true, 'plan_hash' => $qaHash, 'code_hash' => $plan['code_hash'], 'fixture_only' => true];
file_put_contents($qaDirectory.'/territory-backup-proof.json', json_encode($qaProof, JSON_THROW_ON_ERROR));
config(['investment.traceability_directory' => $qaDirectory]);
$service = app(InvestmentTerritorialTraceability::class);
$snapshot = fn () => DB::table('cluster_projects')->orderBy('id')->get(['id', 'source_reference', 'evidence_reference', 'validation_status', 'association_type', 'version_no'])->map(fn ($row) => (array) $row)->all();
$before = $snapshot();
$unchangedTables = ['projects', 'opportunities', 'economic_clusters', 'investment_zones', 'investment_legal_sources', 'investment_dossiers', 'project_locations', 'opportunity_projects', 'opportunity_clusters', 'geo_locations', 'geo_geometries'];
$hashes = fn () => array_combine($unchangedTables, array_map(fn ($table) => hash('sha256', json_encode(DB::table($table)->orderByRaw('to_jsonb('.$table.')::text')->get(), JSON_THROW_ON_ERROR)), $unchangedTables));
$beforeHashes = $hashes();
$actor = DB::table('users')->insertGetId(['name' => 'Territory reversal QA', 'email' => 'territory-reversal-'.bin2hex(random_bytes(5)).'@example.invalid', 'password' => password_hash('QA-only', PASSWORD_BCRYPT), 'created_at' => now(), 'updated_at' => now()]);
$applied = $service->apply($qaHash, $actor);
$replayed = $service->apply($qaHash, $actor);
if ($applied['changed'] !== count($plan['updates']) || ! $replayed['replayed'] || $replayed['changed'] !== 0) {
throw new RuntimeException('Application/idempotence failed.');
}
$sources = $service->sources('economic_clusters', $plan['rows'][array_search('economic_clusters', array_column($plan['rows'], 'kind'))]['record_id']);
$reversed = $service->apply($qaHash, $actor, true, 'Ensaio de reversão na cópia QA restaurada.');
$after = $snapshot();
foreach ($after as $index => $row) {
$expected = $before[$index];
$changed = in_array($row['id'], array_column($plan['updates'], 'id'), true);
$expected['version_no'] += $changed ? 2 : 0;
if ($row !== $expected) {
throw new RuntimeException('Reversal did not preserve values, validation or monotonic version.');
}
}
if ($hashes() !== $beforeHashes || $reversed['changed'] !== count($plan['updates']) || ! $sources) {
throw new RuntimeException('Unrelated data changed or sources not presented.');
}
$evidence = ['verified_at' => gmdate('c'), 'target' => $target, 'plan_hash' => hash('sha256', $activeBytes), 'code_hash' => $plan['code_hash'], 'applied' => $applied, 'replayed' => $replayed, 'reversed' => $reversed, 'source_projection_count' => count($sources), 'unchanged_tables' => $unchangedTables, 'validation_states_preserved' => true, 'versions_monotonic' => true, 'active_writes' => 0];
file_put_contents($directory.'/territory-reversal-evidence.json', json_encode($evidence, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$proof['reversal_verified'] = true;
$proof['plan_hash'] = $evidence['plan_hash'];
$proof['code_hash'] = $evidence['code_hash'];
$proof['reversal_evidence'] = 'territory-reversal-evidence.json';
file_put_contents($proofFile, json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
echo json_encode($evidence, JSON_THROW_ON_ERROR).PHP_EOL;