*/ public function run(int $batchSize = 100): array { $counts = []; foreach (['identity' => ['users', 'identity_links', 'user_id'], 'sector' => ['sectors', 'sector_links', 'sector_id'], 'business' => ['existing_businesses', 'business_links', 'legacy_business_id']] as $domain => [$source, $target, $key]) { // Start again on each invocation to detect edits behind a previous checkpoint. $last = null; $processed = 0; do { $batch = DB::transaction(function () use ($domain, $source, $target, $key, $last, $processed, $batchSize) { DB::select('SELECT pg_advisory_xact_lock(290926,170004)'); $query = DB::table('public.'.$source)->orderBy('id')->limit($batchSize)->lockForUpdate(); if ($last !== null) { $query->where('id', '>', $last); } $rows = $query->get(); $checksums = []; foreach ($rows as $row) { $checksum = hash('sha256', json_encode((array) $row, JSON_THROW_ON_ERROR)); $checksums[] = $checksum; $stableId = (string) Uuid::uuid5(Uuid::NAMESPACE_URL, 'investmoz:'.$domain.':'.$row->id); $data = ['id' => $stableId, $key => $row->id, 'source_system' => 'public.'.$source, 'source_checksum' => $checksum, 'status' => 'pending']; if ($domain === 'sector') { $data['canonical_sector_id'] = $stableId; $data['source_code'] = $row->slug; $data['status'] = $row->is_active && $row->deleted_at === null ? 'active' : 'pending'; } $existing = DB::table('mne.'.$target)->where($key, $row->id)->first(); if (! $existing) { DB::table('mne.'.$target)->insert($data); } elseif ($existing->source_checksum !== $checksum) { // Changed sources invalidate previous reconciliation; never activate by inference. DB::table('mne.'.$target)->where($key, $row->id)->update(['source_checksum' => $checksum, 'status' => 'pending']); } if ($domain !== 'sector' && (! $existing || $existing->status !== 'active' || $existing->source_checksum !== $checksum)) { DB::table('mne.compatibility_rejections')->upsert([[ 'id' => $stableId, 'domain' => $domain, 'source_key' => (string) $row->id, 'code' => 'CANONICAL_MAPPING_REQUIRED', 'cause' => 'Explicit canonical mapping and reconciliation required', 'resolved_at' => null, 'resolution' => null, ]], ['domain', 'source_key', 'code'], ['cause', 'resolved_at', 'resolution']); } } if ($rows->isNotEmpty() || $processed === 0) { DB::table('mne.compatibility_checkpoints')->updateOrInsert(['domain' => $domain], [ 'last_key' => $rows->isEmpty() ? $last : (string) $rows->last()->id, 'processed' => $processed + $rows->count(), 'checksum' => hash('sha256', implode('', $checksums)), 'updated_at' => now(), ]); } return $rows; }); $processed += $batch->count(); if ($batch->isNotEmpty()) { $last = $batch->last()->id; } } while ($batch->count() === $batchSize); $counts[$domain] = $processed; } return $counts; } }