make(Kernel::class)->bootstrap(); $bundle = getenv('FIX_BUNDLE'); require $bundle.'/integrations/sgf/operational/bootstrap.php'; $db = DB::connection(); $runtime = getenv('FIX_RUNTIME'); if (! preg_match('/^sgf_reproduction_restore_[a-f0-9]{12}$/D', $db->getDatabaseName()) || ! preg_match('/^fix_app_[a-f0-9]{12}$/D', $runtime)) { throw new RuntimeException('Isolated recovery target required.'); } $baseline = json_decode(file_get_contents(getenv('FIX_WORK').'/baseline.json'), true, 64, JSON_THROW_ON_ERROR); $q = fn ($name) => '"'.str_replace('"', '""', $name).'"'; $db->statement('GRANT USAGE ON SCHEMA public TO '.$q($runtime)); foreach ($baseline['runtime_table_acl'] as $table => $rights) { if ($rights) { $db->statement('GRANT '.implode(', ', $rights).' ON TABLE public.'.$q($table).' TO '.$q($runtime)); } if (in_array('INSERT', $rights, true) && $db->getSchemaBuilder()->hasColumn($table, 'id')) { $seq = $db->selectOne('select pg_get_serial_sequence(?,?) as name', ['public.'.$table, 'id'])->name; if ($seq) { $db->statement('GRANT USAGE ON SEQUENCE '.$seq.' TO '.$q($runtime)); } } } $db->statement('REVOKE ALL ON ALL FUNCTIONS IN SCHEMA public FROM PUBLIC'); foreach ($baseline['runtime_functions'] as $function) { $db->statement('GRANT EXECUTE ON FUNCTION '.$function.' TO '.$q($runtime)); } foreach (SqlPrivileges::plan($runtime)['statements'] as $sql) { $db->statement($sql); } $expected = json_decode(file_get_contents(getenv('FIX_WORK').'/expected.json'), true, 64, JSON_THROW_ON_ERROR); $fingerprints = []; foreach (array_keys($expected) as $table) { $rows = $db->select('select to_jsonb(t)::text as data from "'.$table.'" t order by to_jsonb(t)::text'); $fingerprints[$table] = hash('sha256', json_encode(array_column($rows, 'data'), JSON_THROW_ON_ERROR)); } if ($fingerprints !== $expected) { throw new RuntimeException('Recovered data differs.'); } config(['database.connections.recovered_runtime' => [...$db->getConfig(), 'username' => $runtime, 'password' => getenv('FIX_RUNTIME_PASSWORD'), 'url' => null]]); $rt = DB::connection('recovered_runtime'); if ($rt->table('ir_requests')->value('status') !== 'approved' || $rt->table('cb_budget_entries')->where('kind', 'reserve')->count() !== 1) { throw new RuntimeException('Recovered runtime cannot consult original decision or reservation.'); } if ($rt->selectOne("select has_schema_privilege(current_user,'public','CREATE') as allowed")->allowed) { throw new RuntimeException('Recovery escalated runtime role.'); } $receipt = $rt->table('sgc_installations')->first(); if ($receipt->schema_signature !== Installation::signature($rt, Installation::tables())) { throw new RuntimeException('Recovered schema differs.'); } echo json_encode(['verified' => true, 'tables_reconciled' => count($fingerprints), 'approved_origin_reservation_versions_preserved' => true, 'runtime_read_verified' => true, 'runtime_ddl_denied' => true, 'schema_and_triggers_verified' => true, 'sql_acl_reapplied_only_to_isolated_role' => true], JSON_THROW_ON_ERROR);