Skip to content

Commit c6e2a3f

Browse files
committed
ci(index): read-only audit of RAR4 reprocessing population
1 parent ad400d6 commit c6e2a3f

2 files changed

Lines changed: 158 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Audit RAR reprocessing population
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- diagnose-rar-reprocessing
8+
9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: audit-rar-reprocessing
14+
cancel-in-progress: false
15+
16+
jobs:
17+
audit:
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 15
20+
env:
21+
INDEX_DATABASE_URL: ${{ secrets.INDEX_DATABASE_URL }}
22+
steps:
23+
- uses: actions/checkout@v7
24+
25+
- uses: pnpm/action-setup@v6
26+
with:
27+
version: 10.32.1
28+
29+
- uses: actions/setup-node@v7
30+
with:
31+
node-version: 22
32+
cache: pnpm
33+
34+
- name: Install dependencies
35+
run: pnpm install --frozen-lockfile
36+
37+
- name: Run read-only population audit
38+
run: pnpm --dir apps/index exec tsx audit-rar-reprocessing.mjs
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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

Comments
 (0)