82 lines
5.7 KiB
PHP
82 lines
5.7 KiB
PHP
<?php
|
|
|
|
// Guarded read-only trace of the existing, explicitly synthetic A/B dossiers. No credentials or ciphertexts in evidence.
|
|
use App\Models\SimulationFormDraft;
|
|
use App\Services\VirtualDesk;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
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();
|
|
portalQaAssertTarget($target);
|
|
DB::beginTransaction();
|
|
DB::statement('SET TRANSACTION READ ONLY');
|
|
$dir = storage_path('app/private/investmoz-v2/portal-audit');
|
|
$prior = json_decode(file_get_contents($dir.'/investor-ab-chromium-20261010.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
$proof = ['captured_at' => gmdate('c'), 'target' => $target, 'read_only' => true, 'active_writes' => 0, 'scenarios' => []];
|
|
foreach ($prior['scenarios'] as $name => $entry) {
|
|
$case = DB::table('desk_cases')->where('id', $entry['id'])->firstOrFail();
|
|
abort_unless((int) $case->user_id === 18 && $case->simulation_id === $entry['draft_id'], 403);
|
|
$desk = app(VirtualDesk::class);
|
|
$payload = $desk->decode($case->payload);
|
|
$path = $payload['investor_path'];
|
|
$result = collect($path['results'])->firstWhere('id', $entry['result_id']);
|
|
abort_unless($result && $result['draft_id'] === $case->simulation_id, 422);
|
|
$draft = SimulationFormDraft::findOrFail($entry['draft_id']);
|
|
abort_unless((int) $draft->owner_user_id === 18, 403);
|
|
$economic = SimulationFormDraft::decryptSnapshot($draft->economic_snapshot);
|
|
$request = DB::table('desk_requests')->where('id', $entry['request_id'])->where('case_id', $case->id)->firstOrFail();
|
|
$requestPayload = $desk->decode($request->payload);
|
|
$task = DB::table('tasks')->where('id', $entry['task_id'])->where('desk_case_id', $case->id)->firstOrFail();
|
|
abort_unless($task->guidance_key === 'path:S02' && $task->status === 'done', 422);
|
|
$steps = DB::table('journey_steps')->where('journey_id', $case->journey_id)->whereIn('step_code', ['S02', 'S10'])->get();
|
|
$stepProof = [];
|
|
foreach ($steps as $step) {
|
|
$detail = $desk->decode($step->path_detail);
|
|
$stepProof[$step->step_code] = ['state' => $detail['state'], 'recorded_at' => $detail['recorded_at'] ?? null, 'scope' => $detail['scope'] ?? null];
|
|
}
|
|
$timeline = [];
|
|
$requestPrefill = [];
|
|
foreach (DB::table('desk_events')->where('case_id', $case->id)->orderBy('event_order')->get() as $event) {
|
|
$data = $desk->decode($event->payload);
|
|
if ($event->request_id === $request->id && in_array($event->action, ['request_prefill_prepared', 'request_prefill_reused'], true)) {
|
|
$requestPrefill[] = ['action' => $event->action, 'order' => $event->event_order, 'created_at' => $event->created_at,
|
|
'draft_id' => $data['draft_id'] ?? null, 'draft_version' => $data['draft_version'] ?? null, 'fields' => $data['fields'] ?? []];
|
|
}
|
|
if (($event->action === 'path_stage_recorded' && in_array($data['code'] ?? '', ['S02', 'S10'], true))
|
|
|| (in_array($event->action, ['path_task_accepted', 'guidance_task_updated'], true) && ($data['task_id'] ?? '') === $task->id)) {
|
|
$timeline[] = ['order' => $event->event_order, 'action' => $event->action, 'actor_id' => $event->actor_id,
|
|
'created_at' => $event->created_at, 'code' => $data['code'] ?? null, 'state' => $data['state'] ?? null,
|
|
'task_status' => $data['status'] ?? null, 'task_id' => $data['task_id'] ?? null];
|
|
}
|
|
}
|
|
if ($name === 'B') {
|
|
$wait = collect($timeline)->first(fn ($e) => $e['code'] === 'S10' && $e['state'] === 'waiting_external');
|
|
$done = collect($timeline)->first(fn ($e) => $e['task_status'] === 'done');
|
|
abort_unless($wait && $done && $wait['order'] < $done['order'] && $stepProof['S10']['state'] === 'waiting_external', 422);
|
|
}
|
|
$proof['scenarios'][$name] = ['case_id' => $case->id, 'case_version' => $case->version, 'owner_id' => $case->user_id,
|
|
'facts' => $path['facts'], 'steps' => $stepProof, 'payload_sha256' => hash('sha256', $case->payload),
|
|
'draft_id' => $draft->id, 'draft_version' => $draft->version_no, 'draft_snapshot_sha256' => $draft->snapshot_sha256,
|
|
'project_title' => $economic['project_title'] ?? null, 'result_id' => $result['id'], 'result_draft_id' => $result['draft_id'],
|
|
'tool_summary_length' => strlen($economic['project_summary'] ?? ''), 'request_description_length' => strlen($requestPayload['description'] ?? ''),
|
|
'request_payload_fields' => array_keys($requestPayload), 'request_created_at' => $request->created_at,
|
|
'result_draft_version' => $result['draft_version'], 'result_source_key' => $result['source_key'],
|
|
'request_id' => $request->id, 'request_status' => $request->status, 'request_version' => $request->version,
|
|
'request_country' => $requestPayload['country'] ?? null, 'request_origin' => $requestPayload['declared_origin_country_code'] ?? null,
|
|
'task_id' => $task->id, 'task_status' => $task->status, 'task_guidance_key' => $task->guidance_key,
|
|
'task_version' => $task->version_no, 'timeline' => $timeline];
|
|
$proof['scenarios'][$name]['request_prefill_provenance'] = $requestPrefill;
|
|
}
|
|
DB::rollBack();
|
|
$proof['result'] = 'passed_read_only_existing_path';
|
|
$file = $dir.'/investor-ab-remaining-persistence-qa.json';
|
|
if (is_file($file)) {
|
|
copy($file, $dir.'/investor-ab-remaining-persistence-qa-'.gmdate('Ymd_His').'.json');
|
|
}
|
|
file_put_contents($file, json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode(['result' => $proof['result'], 'target' => $target, 'read_only' => true, 'scenarios' => array_keys($proof['scenarios'])]).PHP_EOL;
|