Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit cb5a826548507442c42eff6d75fae8883187ec58
parent fb17b672efcab5d1dd3e7b247f83ce172fa42e7b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 26 Sep 2026 03:57:20 -0400

common: the pre-clean gate never judges a suspect its check did not probe, blocked or not (review fix)

94a9aa62 left unverified only the suspects a BLOCKED check did not reach.
An unblocked check also skips a suspect with no `webpage_url` in its
metadata (nothing to probe it by), and that suspect was then judged on its
old availability.json — an old `public` cleared it for an irreversible
delete. Every tier C suspect missing from `probedIds` is now `unverified`,
whatever the reason. The review's read-only scan found no such video on the
live corpus (79,700 dirs, 1,186 clean candidates), so nothing changes today;
such a video is never cleaned until it has a `webpage_url`.

verifyBeforeClean.test.ts +1: an unblocked check with a no-URL suspect
carrying a stale `public` probes only the other suspect, leaves the no-URL
one unverified and excluded, and keeps the probed public suspect and the
listed video cleanable. It fails with the old blocked-only condition; the
blocked and unblocked cases still pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/controller/verifyBeforeClean.test.ts | 51+++++++++++++++++++++++++++++++++++++++++++++++++--
Mcommon/controller/verifyBeforeClean.ts | 29++++++++++++++++-------------
2 files changed, 65 insertions(+), 15 deletions(-)

diff --git a/common/controller/verifyBeforeClean.test.ts b/common/controller/verifyBeforeClean.test.ts @@ -219,12 +219,20 @@ async function withFakeSource( } } -async function seedProbeable(paths: Paths, id: string): Promise<void> { +async function seedProbeable( + paths: Paths, + id: string, + opts: { noUrl?: boolean } = {}, +): Promise<void> { const dir = path.join(paths.channelsDir, "ch", "data", id); await mkdir(dir, { recursive: true }); await writeFile( path.join(dir, "metadata.info.json"), - JSON.stringify({ id, webpage_url: `https://www.youtube.com/watch?v=${id}` }), + JSON.stringify( + opts.noUrl + ? { id } + : { id, webpage_url: `https://www.youtube.com/watch?v=${id}` }, + ), ); // A STALE verdict from long ago: exactly what must not clear a delete. await writeFile( @@ -307,3 +315,42 @@ test("an unblocked confirmation is judged exactly as before", async () => { }, ); }); + +test("an unblocked confirmation that could not probe a suspect (no webpage_url) leaves it unverified, not judged on its old record", async () => { + await withFakeSource( + [ + 'case "$last" in', + ` *suspect0001*) echo '{"id":"suspect0001","availability":"public"}';;`, + ` *) echo "ERROR: [youtube] x: Video unavailable" >&2; exit 1;;`, + "esac", + ].join("\n"), + async (paths, probes) => { + await seedConfig(paths, "ch", { + handling: "youtube", + url: "https://www.youtube.com/@ch/videos", + }); + await seedProbeable(paths, "listed00001"); + await seedProbeable(paths, "suspect0001"); + // Not listed, and nothing to probe it by — but its old record says + // `public`, which would clear it for delete if it were read. + await seedProbeable(paths, "nourl000001", { noUrl: true }); + + const verdicts = await verifyBeforeClean({ + channelSlug: "ch", + paths, + candidateIds: ["listed00001", "suspect0001", "nourl000001"], + onLog: () => {}, + }); + + // The check was not blocked: it probed the one suspect it could. + assert.deepEqual(await probes(), ["https://www.youtube.com/watch?v=suspect0001"]); + assert.deepEqual([...verdicts.unverified], ["nourl000001"]); + const excluded = excludedIds(verdicts); + assert.ok(excluded.has("nourl000001")); + // The probed public suspect and the listed video stay cleanable. + assert.equal(excluded.has("suspect0001"), false); + assert.equal(excluded.has("listed00001"), false); + await assert.rejects(readFile(paths.autoQueueStateFile, "utf8")); + }, + ); +}); diff --git a/common/controller/verifyBeforeClean.ts b/common/controller/verifyBeforeClean.ts @@ -183,20 +183,23 @@ export async function verifyBeforeClean({ return verdicts; } - // A STOPPED CHECK JUDGES NOTHING IT DID NOT PROBE (release 10, L2 review). - // The confirmation stops at the first rate-limited probe, and every suspect - // after it is left with whatever `availability.json` it already had — which - // can be months old and say `public`. Read below, that stale record would - // clear the video for an irreversible delete. So when the check was - // blocked, every suspect it did not probe is `unverified`: skipped, no - // marker, retried on the next sweep. An unblocked check is judged exactly - // as before. - const unprobed = new Set<string>(); - if (check.blocked) { - const probed = new Set(check.probedIds); - for (const id of suspects) if (!probed.has(id)) unprobed.add(id); + // NOTHING THE CHECK DID NOT PROBE IS JUDGED (release 10, L2 review). A + // suspect the check never asked about still has whatever `availability.json` + // it already had — which can be months old and say `public`. Read below, + // that stale record would clear the video for an irreversible delete. So + // every suspect missing from `probedIds` is `unverified`: skipped, no + // marker, retried on the next sweep. Two ways to be missing: the check + // stopped at a rate-limited probe before reaching it (`blocked`), or it was + // skipped — no `webpage_url` in its metadata, so there was nothing to probe. + // The second was judged on its old record before this; no video on the live + // corpus was in that state (2026-09-26 review scan), and none can be now. + const probed = new Set(check.probedIds); + const unprobed = new Set(suspects.filter((id) => !probed.has(id))); + if (unprobed.size > 0) { log( - `Verify before clean: the source rate-limited the confirmation — ` + + (check.blocked + ? `Verify before clean: the source rate-limited the confirmation — ` + : `Verify before clean: the confirmation could not probe every suspect — `) + `${unprobed.size} suspect(s) it did not reach are left unverified, not judged on an old record.`, ); }