make(Kernel::class)->bootstrap(); portalQaAssertTarget($target); if (($argv[1] ?? '') === 'preview') { config(['geometry-import.enabled' => true, 'portal.managers' => ['geo-manager-qa@example.invalid']]); $actor = User::where('email', 'geo-manager-qa@example.invalid')->firstOrFail(); $plans = DB::transaction(function () use ($actor) { DB::statement('SET TRANSACTION READ ONLY'); return array_map(fn ($level) => app(TerritoryGeometryImport::class)->preview($actor, $level), [1, 2, 3]); }); foreach ($plans as $plan) { echo json_encode(['level' => $plan['level'], 'expected' => $plan['expected'], 'resolved' => $plan['resolved'], 'pending' => $plan['pending'], 'scan_ready' => $plan['scan_ready'], 'errors' => array_values(array_filter($plan['rows'], fn ($row) => str_contains($row['status'], 'conflict') || $row['status'] === 'invalid_geometry'))], JSON_THROW_ON_ERROR).PHP_EOL; } exit; } $snapshot = DB::transaction(function () { DB::statement('SET TRANSACTION READ ONLY'); return [ 'locations' => DB::table('geo_locations')->orderBy('location_type')->orderBy('name')->get(), 'geometries' => DB::table('geo_geometries')->selectRaw("id,external_id,geo_location_id,source_system,properties,data_classification,version_no,deleted_at,encode(ST_AsEWKB(geom),'hex') as geometry_bytes")->orderBy('id')->get(), ]; }); $snapshot['target'] = $target; $snapshot['captured_at'] = gmdate('c'); $snapshot['catalogue_hash'] = hash('sha256', json_encode($snapshot['locations'], JSON_THROW_ON_ERROR)); $snapshot['geometries_hash'] = hash('sha256', json_encode($snapshot['geometries'], JSON_THROW_ON_ERROR)); $snapshot['active_writes'] = 0; if (($argv[1] ?? '') === 'verify') { $baseline = json_decode(file_get_contents(storage_path('app/private/investmoz-v2/portal-audit/geometry-reconciliation-baseline.json')), true, 512, JSON_THROW_ON_ERROR); $oldLocations = json_encode($baseline['locations'], JSON_THROW_ON_ERROR); $locationsPreserved = hash_equals(hash('sha256', $oldLocations), $snapshot['catalogue_hash']); $current = []; foreach ($snapshot['geometries'] as $row) { $current[$row->id] = (array) $row; } foreach ($baseline['geometries'] as $row) { if (! isset($current[$row['id']]) || json_encode($row, JSON_THROW_ON_ERROR) !== json_encode($current[$row['id']], JSON_THROW_ON_ERROR)) { throw new RuntimeException('An original QA geometry changed; preservation proof refused.'); } unset($current[$row['id']]); } if (! $locationsPreserved) { throw new RuntimeException('The existing QA catalogue changed; preservation proof refused.'); } $locationTypes = []; foreach ($snapshot['locations'] as $row) { $locationTypes[$row->id] = $row->location_type; } $new = []; $counts = []; foreach ($snapshot['geometries'] as $row) { if (! $row->deleted_at) { $type = $locationTypes[$row->geo_location_id]; $counts[$type] = ($counts[$type] ?? 0) + 1; } if (isset($current[$row->id])) { $properties = json_decode($row->properties, true, 512, JSON_THROW_ON_ERROR); if ($row->data_classification !== 'internal' || $row->deleted_at || ($properties['investment_site'] ?? null) !== false) { throw new RuntimeException('Unexpected disclosure/lifecycle/purpose of new QA boundary.'); } $new[] = ['id' => $row->id, 'location_id' => $row->geo_location_id, 'type' => $locationTypes[$row->geo_location_id], 'external_id' => $row->external_id, 'properties' => $properties]; } } $imports = DB::transaction(function () { DB::statement('SET TRANSACTION READ ONLY'); return DB::table('audit_events')->where('source_system', 'geometry-import')->where('action', 'geometry_import_applied')->orderBy('occurred_at')->get(['entity_id', 'actor_user_id', 'occurred_at', 'after_data']); }); $proof = ['captured_at' => gmdate('c'), 'target' => $target, 'catalogue_count' => count($snapshot['locations']), 'catalogue_preserved' => true, 'catalogue_hash' => $snapshot['catalogue_hash'], 'original_geometries_preserved' => count($baseline['geometries']), 'new_geometries' => $new, 'geometries_by_level' => $counts, 'total_active' => array_sum($counts), 'authenticated_import_events' => $imports, 'published' => false, 'read_only_verification' => true, 'interface_observed' => false, 'active_writes' => 0]; file_put_contents(storage_path('app/private/investmoz-v2/portal-audit/geometry-crosswalk-preservation-qa.json'), json_encode($proof, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); echo json_encode(['target' => $target, 'catalogue_preserved' => true, 'original_geometries_preserved' => count($baseline['geometries']), 'new_geometries' => count($new), 'counts' => $counts, 'total' => array_sum($counts), 'published' => false, 'active_writes' => 0], JSON_THROW_ON_ERROR).PHP_EOL; exit; } $file = storage_path('app/private/investmoz-v2/portal-audit/geometry-reconciliation-baseline.json'); if (is_file($file)) { $file = dirname($file).'/geometry-reconciliation-snapshot-'.gmdate('Ymd_His').'.json'; } file_put_contents($file, json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); echo json_encode(['target' => $target, 'locations' => count($snapshot['locations']), 'geometries' => count($snapshot['geometries']), 'catalogue_hash' => $snapshot['catalogue_hash'], 'active_writes' => 0], JSON_THROW_ON_ERROR).PHP_EOL;