95 lines
8.4 KiB
PHP
95 lines
8.4 KiB
PHP
<?php
|
|
|
|
// Only read the explicitly guarded QA database; do not alter or publish records.
|
|
use App\Models\ExistingBusiness;
|
|
use App\Models\Opportunity;
|
|
use App\Models\SimulationFormDraft;
|
|
use App\Models\User;
|
|
use App\Services\DeclaredOriginStatistics;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
require dirname(__DIR__).'/vendor/autoload.php';
|
|
require __DIR__.'/portal-qa-environment.php';
|
|
$target = portalQaEnvironment();
|
|
$app = require dirname(__DIR__).'/bootstrap/app.php';
|
|
$app->make(Kernel::class)->bootstrap();
|
|
$identity = portalQaAssertTarget($target);
|
|
$dir = storage_path('app/private/investmoz-v2/portal-audit');
|
|
$load = function (string $name) use ($dir) {
|
|
$r = json_decode(file_get_contents($dir.'/'.$name), true, 512, JSON_THROW_ON_ERROR);
|
|
throw_unless($r['result'] === 'passed', RuntimeException::class, 'Completed browser proof required: '.$name);
|
|
|
|
return $r;
|
|
};
|
|
$entity = $load('consumer-entity-chromium-20261010.json');
|
|
$desk = $load('consumer-stats-chromium-20261010.json');
|
|
$business = $load('consumer-business-chromium-20261010.json');
|
|
$territory = $load('consumer-structures-deep-chromium-20261010.json');
|
|
$workflow = $load('consumer-workflow-chromium-20261010.json');
|
|
$proof = DB::transaction(function () use ($identity, $dir, $entity, $desk, $business, $territory, $workflow) {
|
|
DB::statement('SET TRANSACTION READ ONLY');
|
|
$proof = ['at' => gmdate('c'), 'target' => $identity, 'read_only' => true, 'active_writes' => 0];
|
|
$c = DB::table('investment_company_declarations')->where('id', $entity['entity']['id'])->firstOrFail();
|
|
$p = json_decode(Crypt::decryptString($c->payload), true, 512, JSON_THROW_ON_ERROR);
|
|
throw_unless((int) $c->user_id === 18 && $p['incorporation_country_code'] === 'FR' && (int) $c->version === $entity['entity']['version'], RuntimeException::class, 'Entity ownership/version/country diverged');
|
|
$proof['entity'] = ['id' => $c->id, 'owner' => 18, 'version' => $c->version, 'incorporation' => 'FR', 'residence' => 'PT', 'scope' => 'private company declaration, not organization master'];
|
|
$request = DB::table('desk_requests')->where('id', $desk['stats']['id'])->firstOrFail();
|
|
$payload = app(VirtualDesk::class)->decode($request->payload);
|
|
$submitted = DB::table('desk_events')->where('request_id', $request->id)->where('action', 'submitted')->count();
|
|
$revisions = DB::table('desk_revisions')->where('request_id', $request->id)->count();
|
|
throw_unless($submitted === 1 && $revisions === 1 && $request->protocol === $desk['stats']['protocol'] && (int) $request->version === (int) $desk['stats']['version'] && $payload['country'] === 'PT' && $payload['declared_origin_country_code'] === 'ZA', RuntimeException::class, 'Desk repetition changed version/history');
|
|
throw_unless(DB::table('desk_requests')->where('protocol', $request->protocol)->count() === 1, RuntimeException::class, 'Duplicate protocol');
|
|
$proof['desk'] = ['id' => $request->id, 'status' => $request->status, 'version' => $request->version, 'protocol' => $request->protocol, 'country' => 'PT', 'origin' => 'ZA', 'submitted_events' => $submitted, 'revisions' => $revisions, 'one_protocol' => true];
|
|
$b = ExistingBusiness::findOrFail($business['business']['id']);
|
|
$transitions = DB::table('business_workflow_transitions')->where('existing_business_id', $b->id)->where('to_status', 'submitted')->count();
|
|
throw_unless($b->status === 'submitted' && $transitions === 1 && $b->version_no === $business['business']['version'] && $b->payload['country'] === 'PT' && $b->payload['declared_origin_country_code'] === 'FR', RuntimeException::class, 'Business submission/replay diverged');
|
|
$proof['business'] = ['id' => $b->id, 'status' => $b->status, 'version' => $b->version_no, 'code' => $b->code, 'country' => 'PT', 'origin' => 'FR', 'submitted_transitions' => $transitions];
|
|
foreach (['projects' => 'projects', 'clusters' => 'economic_clusters', 'corridors' => 'development_corridors'] as $kind => $table) {
|
|
$entry = $territory['structures'][$kind];
|
|
$r = DB::table($table)->where('id', $entry['id'])->firstOrFail();
|
|
throw_unless($r->primary_territory_id === $entry['locality']['id'], RuntimeException::class, 'Saved deepest territory diverged');
|
|
$proof['territories'][$kind] = ['id' => $r->id, 'code' => $r->code, 'primary_territory_id' => $r->primary_territory_id, 'version' => $r->territory_version, 'geometry' => $entry['geometry']];
|
|
}
|
|
$o = Opportunity::findOrFail($workflow['workflow']['id']);
|
|
$context = $o->workflow_context;
|
|
$events = $context['events'];
|
|
throw_unless($o->status === 'published' && (int) $context['approval']['actor_id'] === 24 && (int) $context['publication']['actor_id'] === 25 && (int) $o->owner_user_id === 21, RuntimeException::class, 'Independent approval/publication diverged');
|
|
foreach ([['submit', 21], ['request_correction', 24], ['approve', 24], ['publish', 25]] as [$action,$actor]) {
|
|
throw_unless(collect($events)->contains(fn ($e) => $e['action'] === $action && (int) $e['actor_id'] === $actor), RuntimeException::class, 'Workflow event missing');
|
|
}
|
|
throw_unless($context['approval']['hash'] === $context['publication']['hash'], RuntimeException::class, 'Published version differs from approval');
|
|
$proof['workflow'] = ['id' => $o->id, 'category' => $o->opportunity_category, 'owner' => 21, 'approver' => 24, 'publisher' => 25, 'status' => $o->status, 'approval_publication_same_hash' => true, 'events' => array_map(fn ($e) => ['actor' => $e['actor_id'], 'action' => $e['action'], 'from' => $e['from'], 'to' => $e['to']], $events), 'visibility' => $context['publication']['visibility']];
|
|
foreach (['opportunity_documents' => $workflow['workflow']['document'], 'opportunity_images' => $workflow['workflow']['image']] as $table => $id) {
|
|
$r = DB::table($table)->where('id', $id)->firstOrFail();
|
|
$d = DB::table('documents')->where('id', $r->document_id)->firstOrFail();
|
|
throw_unless($d->malware_scan_status === 'clean' && $d->malware_engine === 'ClamAV' && trim($d->sha256) === $d->malware_scanned_sha256, RuntimeException::class, 'Own original scan missing');
|
|
$proof['workflow_files'][$table] = ['id' => $id, 'document_id' => $d->id, 'sha256' => trim($d->sha256), 'scan' => 'clean', 'engine' => $d->malware_engine];
|
|
if ($table === 'opportunity_images') {
|
|
foreach (json_decode($r->payload, true)['derivatives'] as $derived) {
|
|
$d = DB::table('documents')->where('id', $derived['document_id'])->firstOrFail();
|
|
throw_unless($d->malware_scan_status === 'clean' && $d->malware_engine === 'ClamAV' && trim($d->sha256) === $d->malware_scanned_sha256, RuntimeException::class, 'Own derivative scan missing');
|
|
$proof['workflow_files']['derivatives'][] = ['document_id' => $d->id, 'sha256' => trim($d->sha256), 'engine' => $d->malware_engine, 'scan' => 'clean'];
|
|
}
|
|
}
|
|
}
|
|
$old = json_decode(file_get_contents($dir.'/investor-ab-remaining-persistence-qa.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
foreach ($old['scenarios'] as $name => $entry) {
|
|
$d = SimulationFormDraft::findOrFail($entry['draft_id']);
|
|
$request = DB::table('desk_requests')->where('id', $entry['request_id'])->firstOrFail();
|
|
$task = DB::table('tasks')->where('id', $entry['task_id'])->firstOrFail();
|
|
throw_unless($d->snapshot_sha256 === $entry['draft_snapshot_sha256'] && (int) $d->version_no === (int) $entry['draft_version'] && $request->status === $entry['request_status'] && (int) $request->version === (int) $entry['request_version'] && $task->status === $entry['task_status'] && (int) $task->version_no === (int) $entry['task_version'], RuntimeException::class, 'A/B original criterion changed');
|
|
$proof['ab_preserved'][$name] = ['draft_snapshot' => true, 'original_draft_request' => true, 'independent_task' => true];
|
|
}
|
|
$manager = User::findOrFail(14);
|
|
config(['portal.managers' => [$manager->email]]);
|
|
$proof['statistics'] = app(DeclaredOriginStatistics::class)->forManager($manager);
|
|
$proof['result'] = 'passed_consumer_persistence_read_only';
|
|
|
|
return $proof;
|
|
});
|
|
file_put_contents($dir.'/consumer-final-persistence-qa.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
|
|
echo json_encode(['result' => $proof['result'], 'target' => $identity, 'active_writes' => 0]).PHP_EOL;
|