86 lines
7.2 KiB
PHP
86 lines
7.2 KiB
PHP
<?php
|
|
// New destination snapshot clone only. No active runtime/provider bootstrap or active writes.
|
|
ini_set('zend.exception_ignore_args', '1');
|
|
set_exception_handler(static function (Throwable $e): void {
|
|
fwrite(STDERR, 'Isolated compatibility verification stopped: '.$e::class.' at '.basename($e->getFile()).':'.$e->getLine().PHP_EOL);
|
|
exit(1);
|
|
});
|
|
$root = dirname(__DIR__);
|
|
$private = $root.'/storage/app/private/investmoz-v2/portal-audit';
|
|
$plan = json_decode(file_get_contents($private.'/service-followup-latest.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
$backup = json_decode(file_get_contents($private.'/workflow-execution-target-backup.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
$candidate = json_decode(file_get_contents($private.'/consolidated-candidate-latest.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
require $plan['source'].'/qa-autoload.php';
|
|
throw_unless($backup['result'] === 'passed' && $backup['files_verified'] && $backup['restore_verified']
|
|
&& $backup['candidate_revision'] === $candidate['revision'] && $backup['candidate_zip_sha256'] === $candidate['zip_sha256'], RuntimeException::class);
|
|
$manifest = json_decode(file_get_contents($candidate['output'].'/manifesto.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
foreach ($manifest['payload'] as $name => $hash) {
|
|
throw_unless(is_file($plan['source'].'/'.$name) && hash_file('sha256', $plan['source'].'/'.$name) === $hash, RuntimeException::class, 'Consolidated QA payload differs.');
|
|
}
|
|
$database = $backup['restored_target']['database'];
|
|
throw_unless((int) $backup['restored_target']['port'] === 55441 && preg_match('/^workflow_consolidated_target_\d{8}_\d{6}_test$/D', $database), RuntimeException::class);
|
|
$app = (static fn ($path) => require $path)($plan['source'].'/bootstrap/app.php');
|
|
$app->useEnvironmentPath($plan['source']);
|
|
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
|
|
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
|
|
$app['env'] = 'testing';
|
|
config(['database.default' => 'pgsql', 'database.connections.pgsql.url' => null,
|
|
'database.connections.pgsql.host' => '127.0.0.1', 'database.connections.pgsql.port' => 55441,
|
|
'database.connections.pgsql.database' => $database, 'database.connections.pgsql.username' => 'postgres',
|
|
'database.connections.pgsql.password' => '', 'database.connections.pgsql.read' => null,
|
|
'database.connections.pgsql.write' => null]);
|
|
$app->register(Illuminate\Database\DatabaseServiceProvider::class);
|
|
Illuminate\Support\Facades\Facade::setFacadeApplication($app);
|
|
$db = $app->make('db')->connection();
|
|
$pdo = $db->getPdo();
|
|
$identity = $pdo->query('SELECT current_database() AS database,inet_server_port() AS port')->fetch(PDO::FETCH_ASSOC);
|
|
throw_unless($identity['database'] === $database && (int) $identity['port'] === 55441, RuntimeException::class);
|
|
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, 'Restored baseline differs before rehearsal.');
|
|
}
|
|
$sequenceSQL = "SELECT * FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename";
|
|
$sequences = $pdo->query($sequenceSQL)->fetchAll(PDO::FETCH_ASSOC);
|
|
$resources = 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';
|
|
$countTables = fn () => (int) $pdo->query("SELECT count(*) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema')")->fetchColumn();
|
|
$beforeColumns = $pdo->query("SELECT table_name,column_name,data_type FROM information_schema.columns WHERE table_schema='public' ORDER BY table_name,ordinal_position")->fetchAll(PDO::FETCH_ASSOC);
|
|
$before = $countTables();
|
|
$db->beginTransaction();
|
|
try {
|
|
$resources->up();
|
|
$after030 = $countTables();
|
|
$profiles->up();
|
|
$after040 = $countTables();
|
|
throw_unless($after030 === $before + 3 && $after040 === $before + 7, RuntimeException::class, 'Unexpected schema delta.');
|
|
$addedColumns = count($pdo->query("SELECT table_name,column_name,data_type FROM information_schema.columns WHERE table_schema='public' AND table_name IN ('documents','users','opportunities')")->fetchAll(PDO::FETCH_ASSOC))
|
|
- count(array_filter($beforeColumns, fn ($column) => in_array($column['table_name'], ['documents','users','opportunities'], true)));
|
|
throw_unless($addedColumns === 5, RuntimeException::class, 'Unexpected column delta.');
|
|
$profiles->down();
|
|
$resources->down();
|
|
} finally {
|
|
$db->rollBack();
|
|
}
|
|
throw_unless($countTables() === $before && $pdo->query($sequenceSQL)->fetchAll(PDO::FETCH_ASSOC) === $sequences
|
|
&& $pdo->query("SELECT table_name,column_name,data_type FROM information_schema.columns WHERE table_schema='public' ORDER BY table_name,ordinal_position")->fetchAll(PDO::FETCH_ASSOC) === $beforeColumns, RuntimeException::class, 'Isolated reversal changed schema/sequences.');
|
|
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, 'Isolated reversal changed original rows.');
|
|
}
|
|
$resourceFingerprint = $app->make(App\Services\OpportunityResourceInstallation::class)->fingerprint();
|
|
$profileFingerprint = $app->make(App\Services\PortalProfileInstallation::class)->fingerprint();
|
|
throw_unless($resourceFingerprint === $manifest['resources_fingerprint'] && $profileFingerprint === $manifest['profile_fingerprint'], RuntimeException::class, 'Installer source fingerprint differs.');
|
|
$proof = ['verified_at' => gmdate('c'), 'result' => 'passed', 'target' => $identity,
|
|
'candidate_revision' => $candidate['revision'], 'code_revision' => $manifest['code_revision'],
|
|
'resources_fingerprint' => $resourceFingerprint, 'profile_fingerprint' => $profileFingerprint,
|
|
'tables_before' => $before, 'tables_after_030000' => $after030, 'tables_after_040000' => $after040,
|
|
'added_columns' => $addedColumns, 'columns_and_original_rows_and_sequences_preserved' => true,
|
|
'COUNTRY02_050000_preserved' => true, 'DDL_compatibility_verified_on_new_snapshot_clone' => true,
|
|
'empty_schema_transactional_reversal_verified' => true,
|
|
'live_or_post_use_restoration_not_performed' => true, 'authenticated_installation_still_required_on_destination' => true,
|
|
'bootstrap_not_executed' => true, 'functional_QA_proofs_reused' => ['A/B', 'C37', 'coediting', 'six_categories', 'LocalService', 'crossed_bootstrap'],
|
|
'scope' => 'Compatibility/reversal of additive 030000 then040000 on the fresh read-only destination snapshot, rolled back; not a live installation or new functional closure.',
|
|
'active_writes' => 0];
|
|
file_put_contents($private.'/workflow-execution-preflight-qa.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode($proof, JSON_THROW_ON_ERROR).PHP_EOL;
|