49 lines
2.8 KiB
PHP
49 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
final class ReconcileMneCompatibility extends Command
|
|
{
|
|
protected $signature = 'mne:reconcile-compatibility {--fail-on-difference : Return failure while any reconciliation or activation gate remains open}';
|
|
|
|
protected $description = 'Read-only compatibility counts, fingerprints and activation blockers';
|
|
|
|
public function handle(): int
|
|
{
|
|
$report = DB::transaction(function (): array {
|
|
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY');
|
|
$domains = [];
|
|
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]) {
|
|
$checksums = [];
|
|
$stale = 0;
|
|
$missing = 0;
|
|
foreach (DB::table('public.'.$source)->orderBy('id')->cursor() as $row) {
|
|
$checksum = hash('sha256', json_encode((array) $row, JSON_THROW_ON_ERROR));
|
|
$checksums[] = $checksum;
|
|
$link = DB::table('mne.'.$target)->where($key, $row->id)->first();
|
|
$missing += $link === null ? 1 : 0;
|
|
$stale += $link !== null && $link->source_checksum !== $checksum ? 1 : 0;
|
|
}
|
|
$domains[$domain] = ['source_count' => count($checksums), 'link_count' => DB::table('mne.'.$target)->count(),
|
|
'pending_count' => DB::table('mne.'.$target)->where('status', '<>', 'active')->count(),
|
|
'missing' => $missing, 'stale_checksums' => $stale, 'checksum' => hash('sha256', implode('', $checksums))];
|
|
}
|
|
$role = DB::selectOne('SELECT rolsuper,rolcreaterole FROM pg_roles WHERE rolname=current_user');
|
|
|
|
return ['domains' => $domains, 'unresolved_rejections' => DB::table('mne.compatibility_rejections')->whereNull('resolved_at')->count(),
|
|
'runtime_is_superuser' => (bool) $role->rolsuper, 'runtime_can_create_roles' => (bool) $role->rolcreaterole,
|
|
'canonical_reads_ready' => false, 'canonical_writes_ready' => false,
|
|
'blockers' => ['Full canonical response mapping and authorization parity not certified',
|
|
'Legacy intake does not capture a sector', 'Runtime privilege separation requires verification with deployment credentials']];
|
|
});
|
|
$this->line(json_encode($report, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
|
|
// This release has no certified canonical adapter: strict gate checks cannot succeed yet.
|
|
return $this->option('fail-on-difference') ? self::FAILURE : self::SUCCESS;
|
|
}
|
|
}
|