Files
Investidor-V2/tools/portal-next-persistence-qa.php
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

97 lines
8.9 KiB
PHP

<?php
use App\Models\InvestmentSubmission;
use App\Models\Opportunity;
use App\Models\SimulationFormDraft;
use App\Services\OpportunityCanonicalContract;
use App\Services\OpportunityManagement;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
$root = dirname(__DIR__);
require $root.'/vendor/autoload.php';
require __DIR__.'/portal-qa-environment.php';
$target = portalQaEnvironment();
$app = require $root.'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$identity = portalQaAssertTarget($target);
$dir = storage_path('app/private/investmoz-v2/portal-audit');
$mode = $argv[1] ?? 'final';
throw_unless(in_array($mode, ['before', 'final'], true), RuntimeException::class, 'Explicit proof mode required');
$proof = DB::transaction(function () use ($identity, $dir, $mode) {
DB::statement('SET TRANSACTION READ ONLY');
$proof = ['at' => gmdate('c'), 'target' => $identity, 'read_only' => true, 'active_writes' => 0];
$source = json_decode(file_get_contents($dir.'/next-workflow-categories-chromium-20261010.json'), true, 512, JSON_THROW_ON_ERROR);
foreach ($mode === 'before' ? [31, 32, 33, 34, 35] : [31, 32, 33, 34, 35, 42, 44, 46, 48] as $id) {
$o = Opportunity::findOrFail($id);
$p = app(OpportunityManagement::class)->values($o)['canonical'];
$c = $o->workflow_context;
$entry = ['id' => $id, 'category' => $o->opportunity_category, 'status' => $o->status, 'working_state' => $c['working_state'], 'revision' => $c['content_revision'] ?? null, 'approval_hash' => $c['approval']['hash'] ?? null, 'publication_hash' => $c['publication']['hash'] ?? null,
'approver' => $c['approval']['actor_id'] ?? null, 'publisher' => $c['publication']['actor_id'] ?? null, 'decisions_preserved' => count($c['decision_history'] ?? []), 'publication_history' => count($c['publication_history'] ?? []),
'review_cycle' => $c['review_cycle'] ?? null, 'review_task_binding_history' => $c['review_task_binding_history'] ?? [],
'canonical_summary_length' => mb_strlen($p['executive_summary'] ?? ''), 'summary_mirror_length' => mb_strlen(app(OpportunityManagement::class)->values($o)['summary'] ?? ''),
'full_reason_preserved' => collect($c['events'] ?? [])->contains(fn ($e) => ($e['actor_id'] ?? 0) === 21 && ($e['action'] ?? '') === 'request_revision' && mb_strlen($e['reason'] ?? '') > 80)];
if ($mode === 'before' && $id !== 32) {
throw_unless(($source['cases'][$id]['original_publication']['hash'] ?? null) === ($c['publication']['hash'] ?? null), RuntimeException::class, 'Previous publication changed before independent reapproval');
$projection = app(OpportunityCanonicalContract::class)->publicProjection($p);
$entry['old_projection_preserved'] = true;
$entry['private_demand_differs_from_published_description'] = $o->description !== $projection['description'];
}
if ($mode === 'final') {
throw_unless($id === 32 ? $c['working_state'] === 'approved' : ($o->status === 'published' && ($c['approval']['hash'] ?? null) === ($c['publication']['hash'] ?? null) && ($c['approval']['actor_id'] ?? null) !== ($c['publication']['actor_id'] ?? null)), RuntimeException::class, 'Final category/independent publication diverged');
}
$proof['categories'][] = $entry;
}
$tasks = DB::table('tasks')->where('source_system', 'opportunity-workflow')->where('status', 'done')->where(function ($q) {
foreach ([31, 32, 33, 34, 35] as $id) {
$q->orWhere('external_id', 'like', 'portal-opportunity:'.$id.':%');
}
})->get(['id', 'task_type', 'outcome', 'version_no']);
foreach ($tasks as $t) {
throw_unless(mb_strlen($t->outcome ?? '') <= 80, RuntimeException::class, 'CRM outcome exceeds its existing summary field');
}$proof['tasks_completed'] = $tasks->map(fn ($t) => ['id' => $t->id, 'type' => $t->task_type, 'outcome_length' => mb_strlen($t->outcome ?? ''), 'version' => $t->version_no])->all();
if ($mode === 'final') {
$r = Opportunity::findOrFail(31);
$h = $r->workflow_context['review_task_binding_history'] ?? [];
throw_unless(count($h) >= 1, RuntimeException::class, 'Legacy completed review repair not demonstrated');
foreach ($h as $e) {
throw_unless(DB::table('tasks')->where('id', $e['previous_task_id'])->where('status', 'done')->exists() && DB::table('tasks')->where('id', $e['task_id'])->exists(), RuntimeException::class, 'Task repair history not preserved');
}
$r = Opportunity::findOrFail(34);
throw_unless(($r->workflow_context['appeal']['pending'] ?? true) === false && $r->workflow_context['rejection']['actor_id'] !== $r->workflow_context['appeal']['decision']['actor_id'], RuntimeException::class, 'Independent appeal not demonstrated');
$proof['appeal'] = ['rejector' => $r->workflow_context['rejection']['actor_id'], 'appeal_reviewer' => $r->workflow_context['appeal']['decision']['actor_id'], 'pending' => false, 'republished' => $r->status === 'published'];
}
$f = json_decode(file_get_contents($dir.'/next-organization-chromium-20261010.json'), true, 512, JSON_THROW_ON_ERROR)['fixture'];
$org = DB::table('organizations')->where('id', $f['organization'])->firstOrFail();
$company = DB::table('investment_company_declarations')->where('id', $f['company'])->firstOrFail();
$cp = json_decode(Crypt::decryptString($company->payload), true, 512, JSON_THROW_ON_ERROR);
$old = InvestmentSubmission::findOrFail($f['old_submission']);
$new = InvestmentSubmission::findOrFail($f['new_submission']);
throw_unless($org->incorporation_country_code === 'ZA' && $cp['incorporation_country_code'] === 'FR' && $old->payload['incorporation_country_code'] === 'MZ' && $old->version === $f['old_version'] && $new->payload['incorporation_country_code'] === 'FR' && $new->payload['organization_country_snapshot']['organization_version'] === $f['master_version'], RuntimeException::class, 'Historical country copies diverged');
$proof['countries'] = ['organization' => $org->id, 'source_system' => $org->source_system, 'external_id' => $org->external_id, 'master' => 'ZA', 'master_version' => $org->version_no, 'company' => 'FR', 'old_submission' => 'MZ', 'old_version' => $old->version, 'new_submission' => 'FR', 'source_version' => $f['master_version'], 'new_organizations' => 0];
$docs = json_decode(file_get_contents($dir.'/next-sharing-chromium-20261010.json'), true, 512, JSON_THROW_ON_ERROR)['documents'];
foreach ($docs as $d) {
$row = DB::table('partnership_documents as p')->join('documents as d', 'd.id', '=', 'p.document_id')->where('p.id', $d['id'])->firstOrFail(['p.id', 'p.shared', 'p.owner_id', 'd.sha256', 'd.malware_scan_status', 'd.malware_scanned_sha256']);
throw_unless(! $row->shared && (int) $row->owner_id === 21 && trim($row->sha256) === $d['sha256'] && $row->malware_scan_status === 'clean' && $row->malware_scanned_sha256 === trim($row->sha256), RuntimeException::class, 'Revocation/original scan diverged');
$proof['documents'][] = ['id' => $row->id, 'shared' => false, 'owner' => 21, 'sha256' => trim($row->sha256), 'same_session_404' => $d['same_session_after_revocation'], 'fresh_session_404' => $d['fresh_session_after_revocation'], 'scan' => 'clean'];
}
$oldProof = json_decode(file_get_contents($dir.'/investor-ab-remaining-persistence-qa.json'), true, 512, JSON_THROW_ON_ERROR);
foreach ($oldProof['scenarios'] as $name => $entry) {
$draft = 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($draft->snapshot_sha256 === $entry['draft_snapshot_sha256'] && (int) $draft->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, 'Prior accepted A/B criterion altered');
$proof['ab_preserved'][$name] = true;
}
$accepted = Opportunity::findOrFail(52);
throw_unless($accepted->workflow_context['approval']['actor_id'] === 24 && $accepted->workflow_context['publication']['actor_id'] === 25 && $accepted->status === 'published', RuntimeException::class, 'Previously accepted opportunity changed');
$proof['accepted_opportunity_52_preserved'] = true;
$proof['result'] = 'passed';
return $proof;
});
$file = $mode === 'before' ? 'next-workflow-before-republication-qa.json' : 'next-final-persistence-qa.json';
file_put_contents($dir.'/'.$file, json_encode($proof, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
echo json_encode(['result' => $proof['result'], 'mode' => $mode, 'target' => $identity, 'active_writes' => 0]).PHP_EOL;