74 lines
4.9 KiB
PHP
74 lines
4.9 KiB
PHP
<?php
|
|
|
|
use App\Models\User;
|
|
use App\Services\OpportunityOriginReview;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Vite;
|
|
|
|
$root = dirname(__DIR__);
|
|
$directory = $root.'/storage/app/private/investmoz-v2';
|
|
$proof = json_decode(file_get_contents($directory.'/opportunity-origin-backup-proof.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
$target = $proof['restored_target'];
|
|
if (! $proof['restore_verified'] || ! preg_match('/^desk_restore_\d{8}_\d{6}_test$/', $target['database']) || (int) $target['port'] !== 55441) {
|
|
throw new RuntimeException('Isolated restored QA required.');
|
|
}
|
|
foreach (['APP_ENV' => 'testing', 'APP_DEBUG' => 'true', 'APP_URL' => 'http://127.0.0.1:18094', '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' => '', 'SESSION_DRIVER' => 'file', 'SESSION_COOKIE' => 'origin_review_qa', '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();
|
|
$c = config('database.connections.pgsql');
|
|
if (config('database.default') !== 'pgsql' || $c['host'] !== '127.0.0.1' || (int) $c['port'] !== 55441 || $c['database'] !== $target['database'] || ! empty($c['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 database.');
|
|
}
|
|
$qa = $directory.'/qa-origin-review';
|
|
if (! is_dir($qa.'/sessions')) {
|
|
mkdir($qa.'/sessions', 0700, true);
|
|
}
|
|
config(['session.files' => $qa.'/sessions', 'portal.managers' => ['origin-browser@example.invalid'], 'opportunity_origin.proof_path' => $qa.'/proof.json', 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
|
|
Vite::useHotFile($root.'/storage/framework/origin-qa-absent-hot');
|
|
if (PHP_SAPI === 'cli' && ($argv[1] ?? '') === 'seed') {
|
|
$user = User::updateOrCreate(['email' => 'origin-browser@example.invalid'], ['name' => 'Origin review QA', 'password' => Hash::make('Origin-QA-2026!')]);
|
|
$user->forceFill(['email_verified_at' => now()])->save();
|
|
file_put_contents($qa.'/proof.json', json_encode([...$proof, 'target' => $target, 'reversal_verified' => true, 'fixture_only' => true, 'code_hash' => app(OpportunityOriginReview::class)->fingerprint()], JSON_THROW_ON_ERROR));
|
|
echo json_encode(['target' => $target, 'qa_user' => $user->id, 'code_hash' => app(OpportunityOriginReview::class)->fingerprint(), 'active_writes' => 0]).PHP_EOL;
|
|
exit;
|
|
}
|
|
if (PHP_SAPI === 'cli' && ($argv[1] ?? '') === 'verify') {
|
|
$hashes = [];
|
|
foreach (['opportunities', 'opportunity_projects', 'opportunity_clusters', 'opportunity_favourites', 'interest_requests', 'interest_submission_receipts', 'investment_messages', 'tasks', 'project_locations', 'cluster_projects'] as $table) {
|
|
$qualified = '"public"."'.$table.'"';
|
|
$rows = DB::select('SELECT row_to_json(t)::text AS row_json FROM '.$qualified.' t ORDER BY row_to_json(t)::text');
|
|
$hashes[$qualified] = hash('sha256', implode("\n", array_map(fn ($row) => $row->row_json, $rows)));
|
|
if ($hashes[$qualified] !== $proof['table_hashes'][$qualified]) {
|
|
throw new RuntimeException('Record/interaction preservation failed: '.$table);
|
|
}
|
|
}
|
|
$events = DB::table('audit_events')->where('source_system', 'opportunity-origin-review')->orderBy('occurred_at')->get();
|
|
$state = app(OpportunityOriginReview::class)->state(1);
|
|
if ($state['classification'] !== 'undetermined' || $events->count() !== 2) {
|
|
throw new RuntimeException('QA classification cycle incomplete.');
|
|
}
|
|
$evidence = ['verified_at' => gmdate('c'), 'target' => $target, 'hashes' => $hashes, 'classification_restored' => $state['classification'] === 'undetermined', 'decision_events' => $events->count(), 'code_hash' => app(OpportunityOriginReview::class)->fingerprint(), 'active_writes' => 0];
|
|
file_put_contents($directory.'/opportunity-origin-reversal-evidence.json', json_encode($evidence, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode($evidence, JSON_THROW_ON_ERROR).PHP_EOL;
|
|
exit;
|
|
}
|
|
$path = rawurldecode(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/');
|
|
$asset = realpath($root.'/public'.$path);
|
|
$public = realpath($root.'/public');
|
|
if ($path !== '/' && $asset && $public && str_starts_with($asset, $public.DIRECTORY_SEPARATOR) && is_file($asset)) {
|
|
return false;
|
|
}
|
|
header('X-Investmoz-QA-Target: '.$identity->database.':'.$identity->port);
|
|
$app->handleRequest(Request::capture());
|