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;