54 lines
5.1 KiB
PHP
54 lines
5.1 KiB
PHP
<?php
|
|
|
|
// Protected active reader only; no application kernel or migrations.
|
|
require __DIR__.'/investmoz-v2-territory-audit.php';
|
|
$dir = dirname(__DIR__).'/storage/app/private/investmoz-v2';
|
|
$pdo->exec('BEGIN TRANSACTION READ ONLY');
|
|
$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);
|
|
$installed = (bool) $pdo->query("SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='opportunities' AND column_name='management_version')")->fetchColumn();
|
|
$migrationApplied = (int) $pdo->query("SELECT count(*) FROM migrations WHERE migration='2026_10_09_020000_extend_opportunity_management'")->fetchColumn();
|
|
$resourceSchemaInstalled = (bool) $pdo->query("SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='opportunity_images')")->fetchColumn();
|
|
$resourceAuditCount = (int) $pdo->query("SELECT count(*) FROM audit_events WHERE source_system IN ('opportunity-resources','opportunity-resources-install')")->fetchColumn();
|
|
$classifications = $pdo->query("SELECT DISTINCT ON (after_data->>'opportunity_id') after_data->>'opportunity_id' AS id,after_data->>'classification' AS classification FROM audit_events WHERE source_system='opportunity-origin-review' ORDER BY after_data->>'opportunity_id',occurred_at DESC,id DESC")->fetchAll(PDO::FETCH_ASSOC);
|
|
$auditCount = (int) $pdo->query("SELECT count(*) FROM audit_events WHERE source_system IN ('opportunity-management','opportunity-management-install')")->fetchColumn();
|
|
$installation = $pdo->query("SELECT id,actor_user_id,occurred_at,action,after_data FROM audit_events WHERE source_system='opportunity-management-install' ORDER BY occurred_at,id")->fetchAll(PDO::FETCH_ASSOC);
|
|
$backup = json_decode(file_get_contents($dir.'/opportunity-management-backup-proof.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
$checks = [];
|
|
$restored = $backup['restored_target'];
|
|
if (! str_ends_with($restored['database'], '_test') || (int) $restored['port'] !== 55441) {
|
|
throw new RuntimeException('Isolated history comparison required');
|
|
}
|
|
$baseline = new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$restored['database'], $c['username'], $c['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
|
$baseline->exec('BEGIN TRANSACTION READ ONLY');
|
|
$oldEvents = $baseline->query("SELECT id,row_to_json(t)::text AS row FROM audit_events t WHERE source_system IS DISTINCT FROM 'opportunity-management' ORDER BY row_to_json(t)::text")->fetchAll(PDO::FETCH_KEY_PAIR);
|
|
if (hash('sha256', implode("\n", array_values($oldEvents))) !== $backup['table_hashes']['"public"."audit_events"']) {
|
|
throw new RuntimeException('Historical baseline drift');
|
|
}
|
|
$currentEvents = $pdo->query('SELECT id,row_to_json(t)::text AS row FROM audit_events t ORDER BY id')->fetchAll(PDO::FETCH_KEY_PAIR);
|
|
$missing = $changed = 0;
|
|
foreach ($oldEvents as $eventId => $row) {
|
|
if (! isset($currentEvents[$eventId])) {
|
|
$missing++;
|
|
} elseif ($currentEvents[$eventId] !== $row) {
|
|
$changed++;
|
|
}
|
|
}
|
|
$baseline->exec('ROLLBACK');
|
|
$historicalAudit = ['baseline_events' => count($oldEvents), 'missing' => $missing, 'changed' => $changed, 'additional_events' => count($currentEvents) - count($oldEvents)];
|
|
foreach (['opportunities', 'opportunity_projects', 'opportunity_clusters', 'opportunity_favourites', 'interest_requests', 'interest_submission_receipts', 'investment_messages', 'tasks', 'project_locations', 'cluster_projects', 'audit_events'] as $table) {
|
|
$key = '"public"."'.$table.'"';
|
|
if ($table === 'opportunities') {
|
|
$columns = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_schema='public' AND table_name='opportunities' ORDER BY ordinal_position")->fetchAll(PDO::FETCH_COLUMN);
|
|
$columns = array_diff($columns, ['management_version', 'opportunity_category', 'management_draft', 'management_creation_key', 'first_submitted_at', 'management_closed_once']);
|
|
$from = '(SELECT '.implode(',', array_map(fn ($column) => '"'.$column.'"', $columns)).' FROM opportunities)';
|
|
} else {
|
|
$from = $table === 'audit_events' ? "(SELECT * FROM audit_events WHERE source_system IS DISTINCT FROM 'opportunity-management-install')" : $key;
|
|
}
|
|
$rows = $pdo->query('SELECT row_to_json(t)::text FROM '.$from.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN);
|
|
$checks[$table] = ['matches_preinstallation_backup' => hash('sha256', implode("\n", $rows)) === $backup['table_hashes'][$key], 'rows' => count($rows)];
|
|
}
|
|
$pdo->exec('ROLLBACK');
|
|
$result = ['captured_at' => gmdate('c'), 'identity' => $identity, 'installed' => $installed, 'migration_applied' => $migrationApplied, 'resource_schema_installed' => $resourceSchemaInstalled, 'resource_events' => $resourceAuditCount, 'classifications' => $classifications, 'management_events' => $auditCount, 'installation_audit' => $installation, 'preservation' => $checks, 'historical_audit' => $historicalAudit, 'active_writes' => 0];
|
|
file_put_contents($dir.'/opportunity-management-active-read-only.json', json_encode($result, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode($result).PHP_EOL;
|