|
| 1 | +// Disposable, read-only population audit for the RAR4 reprocessing investigation. Never |
| 2 | +// merged into main. Queries only; the session is put into a hard read-only transaction |
| 3 | +// before any SELECT runs, so an edited query cannot mutate production even by accident. |
| 4 | + |
| 5 | +import { Pool } from '@neondatabase/serverless' |
| 6 | + |
| 7 | +const databaseUrl = process.env.INDEX_DATABASE_URL |
| 8 | +if (!databaseUrl) throw new Error('INDEX_DATABASE_URL is required') |
| 9 | + |
| 10 | +// Unique ModWorkshop PD2 mod ids resolved from the 269 historical RAR4-failure URLs in |
| 11 | +// production run 31929156956: 167 matched against live ModWorkshop listing metadata by |
| 12 | +// name, plus 53 more recovered from our own retained mod_listings rows (sync-listings |
| 13 | +// only ever upserts, so a row can outlive its mod's removal from ModWorkshop). No archive |
| 14 | +// bytes were re-downloaded to produce this list. 3 further candidates were ambiguous |
| 15 | +// (multiple listings share the exact same name) and 46 remain unresolved; both are |
| 16 | +// excluded here — see the audit report for the full breakdown. |
| 17 | +const resolvedRemoteIds = [ |
| 18 | + 13804, 12676, 13788, 13792, 13789, 13775, 13769, 13767, 13766, 13751, 13757, 13756, 13752, |
| 19 | + 13712, 13750, 13612, 13739, 13738, 13728, 13727, 13726, 228, 13711, 13719, 13713, 13707, 13700, |
| 20 | + 13706, 13705, 13692, 13681, 13640, 13605, 13593, 13621, 13576, 13579, 13607, 12575, 13604, |
| 21 | + 13602, 13565, 13570, 13560, 13553, 12987, 13547, 13522, 13514, 13503, 13499, 13475, 13397, |
| 22 | + 13462, 13461, 13459, 13162, 13430, 13411, 13402, 13362, 13123, 13350, 13351, 13326, 13285, |
| 23 | + 13268, 12991, 207, 224, 13239, 13238, 13237, 13234, 13230, 13205, 13201, 13195, 13190, 13174, |
| 24 | + 13137, 13121, 13111, 13117, 13115, 13114, 13107, 12978, 13095, 13090, 13084, 13074, 13068, |
| 25 | + 13054, 13053, 13051, 13046, 13033, 13020, 13014, 13011, 12998, 12989, 12990, 12985, 12955, |
| 26 | + 12963, 12939, 12957, 12936, 12915, 12918, 12719, 12898, 12895, 12889, 12885, 12877, 12873, |
| 27 | + 12872, 12863, 12862, 12838, 12835, 12818, 12828, 12820, 12815, 12814, 12760, 12782, 12745, |
| 28 | + 12700, 12695, 1287, 12686, 12653, 12645, 12599, 12570, 12637, 12636, 12634, 12598, 12571, 12583, |
| 29 | + 12553, 12552, 12549, 12544, 364, 12536, 12535, 12534, 12532, 12528, 12523, 12505, 12456, 12421, |
| 30 | + 12432, 12430, 12429, 1518, 12373, 12390, 12379, 266, 13540, 13157, 1410, 1409, 264, 13215, |
| 31 | + 13213, 411, 37, 406, 420, 422, 423, 182, 12933, 442, 12812, 12809, 12657, 12648, 12425, 12591, |
| 32 | + 12526, 574, 12527, 576, 575, 12495, 12354, 329, 331, 12424, 12397, 12395, 678, 666, 665, 659, |
| 33 | + 655, 654, 653, 652, 651, 649, 646, 645, 644, 640, 639, 638, 637, 636, |
| 34 | +] |
| 35 | + |
| 36 | +const pool = new Pool({ connectionString: databaseUrl }) |
| 37 | +const client = await pool.connect() |
| 38 | + |
| 39 | +try { |
| 40 | + await client.query('SET default_transaction_read_only = on') |
| 41 | + await client.query('BEGIN TRANSACTION READ ONLY') |
| 42 | + |
| 43 | + const source = await client.query( |
| 44 | + `SELECT sources.id |
| 45 | + FROM sources |
| 46 | + JOIN games ON games.id = sources.game_id |
| 47 | + WHERE games.slug = 'pd2' AND sources.name = 'modworkshop'` |
| 48 | + ) |
| 49 | + if (source.rows.length !== 1) throw new Error('expected exactly one pd2/modworkshop source') |
| 50 | + const sourceId = source.rows[0].id |
| 51 | + console.log(`pd2 modworkshop source_id = ${sourceId}`) |
| 52 | + |
| 53 | + const rows = await client.query( |
| 54 | + `WITH target(remote_id) AS ( |
| 55 | + SELECT UNNEST($1::BIGINT[]) |
| 56 | + ) |
| 57 | + SELECT |
| 58 | + target.remote_id, |
| 59 | + mod_listings.remote_id IS NOT NULL AS listing_exists, |
| 60 | + mod_listings.has_download, |
| 61 | + mod_listings.updated_at AS listing_updated_at, |
| 62 | + mod_listings.name AS listing_name, |
| 63 | + mod_checks.remote_id IS NOT NULL AS check_exists, |
| 64 | + mod_checks.updated_at AS check_updated_at, |
| 65 | + mod_checks.checked_at, |
| 66 | + mod_checks.file_ids, |
| 67 | + (mod_checks.file_ids = '[]'::jsonb) AS check_empty, |
| 68 | + ( |
| 69 | + mod_listings.has_download |
| 70 | + AND (mod_checks.remote_id IS NULL OR mod_checks.updated_at <> mod_listings.updated_at) |
| 71 | + ) AS currently_pending |
| 72 | + FROM target |
| 73 | + LEFT JOIN mod_listings |
| 74 | + ON mod_listings.source_id = $2 AND mod_listings.remote_id = target.remote_id |
| 75 | + LEFT JOIN mod_checks |
| 76 | + ON mod_checks.source_id = $2 AND mod_checks.remote_id = target.remote_id |
| 77 | + ORDER BY target.remote_id`, |
| 78 | + [resolvedRemoteIds, sourceId] |
| 79 | + ) |
| 80 | + |
| 81 | + let missing = 0 |
| 82 | + let noCheck = 0 |
| 83 | + let settledEmpty = 0 |
| 84 | + let indexed = 0 |
| 85 | + let pending = 0 |
| 86 | + let hasDownloadFalse = 0 |
| 87 | + let other = 0 |
| 88 | + |
| 89 | + for (const row of rows.rows) { |
| 90 | + if (!row.listing_exists) { |
| 91 | + missing++ |
| 92 | + continue |
| 93 | + } |
| 94 | + if (!row.has_download) hasDownloadFalse++ |
| 95 | + if (row.currently_pending) pending++ |
| 96 | + if (!row.check_exists) noCheck++ |
| 97 | + else if (row.check_empty) settledEmpty++ |
| 98 | + else indexed++ |
| 99 | + if (row.check_exists && !row.check_empty && row.currently_pending) other++ |
| 100 | + } |
| 101 | + |
| 102 | + console.log(`total targeted remote_ids: ${resolvedRemoteIds.length}`) |
| 103 | + console.log(`missing (no mod_listings row): ${missing}`) |
| 104 | + console.log(`has_download = false: ${hasDownloadFalse}`) |
| 105 | + console.log(`no mod_checks row at all: ${noCheck}`) |
| 106 | + console.log(`settled-empty (file_ids = '[]'): ${settledEmpty}`) |
| 107 | + console.log(`already indexed (file_ids non-empty): ${indexed}`) |
| 108 | + console.log(`currently pending under production predicate: ${pending}`) |
| 109 | + console.log(`unexpected (indexed but also pending): ${other}`) |
| 110 | + |
| 111 | + console.log('--- full row dump ---') |
| 112 | + for (const row of rows.rows) { |
| 113 | + console.log(JSON.stringify(row)) |
| 114 | + } |
| 115 | + |
| 116 | + await client.query('ROLLBACK') |
| 117 | +} finally { |
| 118 | + client.release() |
| 119 | + await pool.end() |
| 120 | +} |
0 commit comments