43 lines
3.4 KiB
PHP
43 lines
3.4 KiB
PHP
<?php
|
|
|
|
// Direct PDO reader: no application kernel, migrations, seeders or active writes.
|
|
require __DIR__.'/investmoz-v2-territory-audit.php';
|
|
$directory = dirname(__DIR__).'/storage/app/private/investmoz-v2';
|
|
$backup = json_decode(file_get_contents($directory.'/opportunity-origin-backup-proof.json'), true, 512, JSON_THROW_ON_ERROR);
|
|
$pdo->exec('BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ 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);
|
|
$events = $pdo->query("SELECT id,actor_user_id,occurred_at,before_data,after_data FROM audit_events WHERE source_system='opportunity-origin-review' ORDER BY occurred_at,id")->fetchAll(PDO::FETCH_ASSOC);
|
|
$records = $pdo->query("SELECT o.id,o.reference,o.title,o.status,COALESCE((SELECT a.after_data->>'classification' FROM audit_events a WHERE a.source_system='opportunity-origin-review' AND a.after_data->>'opportunity_id'=o.id::text ORDER BY a.occurred_at DESC,a.id DESC LIMIT 1),'undetermined') AS classification FROM opportunities o WHERE o.id BETWEEN 1 AND 6 ORDER BY o.id")->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($records as &$record) {
|
|
$record['public_scope_included'] = $record['status'] === 'published' && $record['classification'] !== 'example';
|
|
}
|
|
unset($record);
|
|
$checks = [];
|
|
$restored = $backup['restored_target'];
|
|
if (! str_ends_with($restored['database'], '_test') || (int) $restored['port'] !== 55441) {
|
|
throw new RuntimeException('Invalid isolated comparison target');
|
|
}
|
|
$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-origin-review' ORDER BY id")->fetchAll(PDO::FETCH_KEY_PAIR);
|
|
$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 $id => $row) {
|
|
if (! isset($currentEvents[$id])) {
|
|
$missing++;
|
|
} elseif ($currentEvents[$id] !== $row) {
|
|
$changed++;
|
|
}
|
|
}
|
|
$baseline->exec('ROLLBACK');
|
|
foreach (['opportunities', 'opportunity_projects', 'opportunity_clusters', 'opportunity_favourites', 'interest_requests', 'interest_submission_receipts', 'investment_messages', 'tasks', 'project_locations', 'cluster_projects'] as $table) {
|
|
$key = '"public"."'.$table.'"';
|
|
$rows = $pdo->query('SELECT row_to_json(t)::text AS row FROM '.$key.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN);
|
|
$hash = hash('sha256', implode("\n", $rows));
|
|
$checks[$table] = ['matches_backup' => $hash === ($backup['table_hashes'][$key] ?? null), 'hash' => $hash, 'rows' => count($rows)];
|
|
}
|
|
$pdo->exec('ROLLBACK');
|
|
$proof = ['captured_at' => gmdate('c'), 'identity' => $identity, 'records' => $records, 'audit' => $events, 'preservation' => $checks, 'historical_audit' => ['baseline_events' => count($oldEvents), 'missing' => $missing, 'changed' => $changed, 'additional_events' => count($currentEvents) - count($oldEvents)], 'active_writes' => 0];
|
|
file_put_contents($directory.'/opportunity-origin-active-confirmation.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR).PHP_EOL;
|