27e8acc592
The sweep stats every track path and marks unreadable files missing, with no
check that /music is mounted. An unmounted or misbehaving bind would fail
every stat and mark the entire library missing in one pass; the ratio is only
recoverable by a full rescan.
Three guards, cheapest first:
- liveness: probe a sample of existing track paths before doing anything;
abort if none are readable
- ratio: abort mid-sweep if the missing fraction crosses a threshold,
leaving already-marked rows alone rather than rolling back a partial pass
- progress: keyset pagination over id with the cursor persisted in
integrity_sweep_state, so a sweep aborted or restarted mid-run resumes
instead of re-walking from the top and re-marking
The repair-corrupted-metadata script shares the same failure mode and gets
the same abort path.
REVIEW-2026-07-30.md secondary finding: integrity sweep has no mount check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
/**
|
|
* One-off cleanup: repair track rows corrupted by an earlier buggy
|
|
* `metadata_refresh` worker job.
|
|
*
|
|
* The bug ran on EVERY refresh and concatenated (no separator) corruption
|
|
* markers directly onto the real values:
|
|
* - tracks.title += ' (Enriched)'
|
|
* - tracks.artist += 'Unknown Artist'
|
|
* Because it ran repeatedly, corruption can be stacked, e.g.
|
|
* title = "Song (Enriched) (Enriched)"
|
|
* artist = "RealName Unknown ArtistUnknown Artist"
|
|
*
|
|
* The repair logic (PASS 1 authoritative rescan + PASS 2 defensive SQL strip,
|
|
* plus issue tracking) now lives in IntegrityService.runSweep(). This script is
|
|
* just a run-once-and-exit entry point that delegates to it, so the regex/rescan
|
|
* logic is never duplicated. Safe to run multiple times (idempotent).
|
|
*/
|
|
import { Client as PgClient } from 'pg';
|
|
import { IntegrityService } from '../integrity.service.js';
|
|
import { queue } from '../queue.js';
|
|
|
|
async function main() {
|
|
const pgClient = new PgClient({
|
|
connectionString: process.env.DATABASE_URL,
|
|
});
|
|
|
|
await pgClient.connect();
|
|
console.log('[Repair] Connected to PostgreSQL');
|
|
|
|
const integrity = new IntegrityService(pgClient, queue);
|
|
const summary = await integrity.runSweep();
|
|
if (summary.aborted) {
|
|
console.error(`[Repair] Sweep ABORTED by safety guard: ${summary.abortReason}`);
|
|
} else {
|
|
console.log(
|
|
`[Repair] Done: detected=${summary.detected} fixed=${summary.fixed} needsReview=${summary.needsReview}`
|
|
);
|
|
}
|
|
|
|
await pgClient.end();
|
|
console.log('[Repair] Connection closed.');
|
|
}
|
|
|
|
main()
|
|
.then(() => process.exit(0))
|
|
.catch((err) => {
|
|
console.error('[Repair] Failed:', err);
|
|
process.exit(1);
|
|
});
|