bootstrap($app); (new LoadConfiguration)->bootstrap($app); $config = $app->make('config'); $connection = $config->get('database.connections.'.$config->get('database.default')); if (($connection['driver'] ?? null) !== 'pgsql' || ! empty($connection['url'])) { throw new RuntimeException('Explicit PostgreSQL configuration required for read-only diagnosis.'); } $pdo = new PDO('pgsql:host='.$connection['host'].';port='.$connection['port'].';dbname='.$connection['database'], $connection['username'], $connection['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $pdo->exec('BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'); try { $identity = $pdo->query("SELECT current_database() AS database,inet_server_port() AS port,current_setting('transaction_read_only') AS read_only")->fetch(PDO::FETCH_ASSOC); $managers = $config->get('portal.managers', []); $accounts = []; foreach ($pdo->query('SELECT id,email,email_verified_at FROM users ORDER BY id') as $user) { $listed = in_array(strtolower($user['email']), $managers, true); if ($listed || in_array((int) $user['id'], [13, 18], true)) { $accounts[] = ['id' => (int) $user['id'], 'listed_manager' => $listed, 'email_verified' => $user['email_verified_at'] !== null, 'manage_portal' => $listed && $user['email_verified_at'] !== null]; } } $columns = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_schema='public' AND table_name='opportunities'")->fetchAll(PDO::FETCH_COLUMN); $migrations = $pdo->query("SELECT migration FROM migrations WHERE migration LIKE '%opportunity_management%' OR migration LIKE '%opportunity_resources%' OR migration LIKE '%prepare_investor_path%' OR migration LIKE '%internal_virtual_desk%' OR migration LIKE '%extend_desk_investor_guidance%'")->fetchAll(PDO::FETCH_COLUMN); $counts = $pdo->query('SELECT status,count(*) AS total FROM opportunities WHERE deleted_at IS NULL GROUP BY status ORDER BY status')->fetchAll(PDO::FETCH_ASSOC); $resourcesInstalled = $pdo->query("SELECT to_regclass('public.opportunity_images') IS NOT NULL")->fetchColumn(); $profilesInstalled = $pdo->query("SELECT to_regclass('public.portal_role_assignments') IS NOT NULL")->fetchColumn(); $pathInstalled = $pdo->query("SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='investor_journeys' AND column_name='path_definition_id')")->fetchColumn(); $pathDetails = ['migration' => '2026_10_09_190000_prepare_investor_path', 'ledger' => in_array('2026_10_09_190000_prepare_investor_path', $migrations, true), 'journey_path_definition_column' => (bool) $pathInstalled, 'step_path_detail_column' => (bool) $pdo->query("SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='journey_steps' AND column_name='path_detail')")->fetchColumn(), 'definitions' => $pdo->query("SELECT id,status,md5(schema_snapshot::text) AS snapshot_md5 FROM questionnaire_versions WHERE code='investor-path-19'")->fetchAll(PDO::FETCH_ASSOC)]; $geometryTable = $pdo->query("SELECT to_regclass('public.geo_geometries') IS NOT NULL")->fetchColumn(); $geometryCount = $geometryTable ? (int) $pdo->query('SELECT count(*) FROM geo_geometries WHERE deleted_at IS NULL')->fetchColumn() : null; } finally { $pdo->exec('ROLLBACK'); } $manifestPath = $root.'/public/build/manifest.json'; $manifest = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR); $assets = []; foreach (['resources/js/app.tsx', 'resources/js/pages/dashboard.tsx', 'resources/js/pages/management/opportunities.tsx', 'resources/js/pages/management/opportunity-draft.tsx'] as $entry) { $file = $manifest[$entry]['file'] ?? null; $bytes = $file ? file_get_contents($root.'/public/build/'.$file) : ''; $assets[$entry] = ['file' => $file, 'sha256' => hash('sha256', $bytes), 'management_label' => str_contains($bytes, 'Gestão de Oportunidades'), 'new_label' => str_contains($bytes, 'Nova oportunidade'), 'old_create_label' => str_contains($bytes, 'Criar oportunidade')]; } $proof = ['captured_at' => gmdate('c'), 'identity' => $identity, 'configured_app_url' => $config->get('app.url'), 'config_cached' => $app->configurationIsCached(), 'routes_cached' => is_file($app->getCachedRoutesPath()), 'public_hot_present' => is_file($root.'/public/hot'), 'manager_configuration_count' => count($managers), 'accounts' => $accounts, 'management_installed' => in_array('management_version', $columns, true), 'resource_extension_installed' => (bool) $resourcesInstalled, 'dashboard_increment_enabled' => (bool) $config->get('portal.opportunity_dashboard_access_enabled'), 'migrations' => $migrations, 'opportunities' => $counts, 'investor_path_installed' => (bool) $pathInstalled, 'path_190000_evidence' => $pathDetails, 'active_geometry_count' => $geometryCount, 'specialized_increment_enabled' => (bool) $config->get('portal.opportunity_specialized_drafts_enabled'), 'profile_authorization_installed' => (bool) $profilesInstalled, 'profile_access_enabled' => (bool) $config->get('portal.profile_access_enabled'), 'session_configuration' => array_intersect_key($config->get('session'), array_flip(['driver', 'cookie', 'domain', 'path', 'secure', 'same_site'])), 'manifest_sha256' => hash_file('sha256', $manifestPath), 'assets' => $assets, 'current_browser_actor_known' => false, 'active_writes' => 0]; $dir = $root.'/storage/app/private/investmoz-v2/portal-audit'; $path = $dir.'/dashboard-active-readonly.json'; if (is_file($path)) { copy($path, $dir.'/dashboard-active-readonly-'.gmdate('Ymd_His').'.json'); } file_put_contents($path, json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); echo json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR).PHP_EOL;