Archilyzer · Source

archilyzer

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

commit 5f3c62f4957b9b2af03cfcdc40bac7af39738a5a
parent 47870ae59c13ab7c4bf47f14e5196b4e93f1e9df
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 21 Sep 2026 02:30:57 -0400

ops: a fan-out that started jobs must say which

`pnpm ops relocate --wait` did not wait. The route answers
`{ok, queued:[slug], skipped:[]}` and carries no `jobId`, so the script's
id-extraction found nothing, took the "not a job-starting action" branch and
returned 0 the instant the response arrived — reporting success about a 130 GB
copy that had not begun. `relocate-back` had it too, and so did every other
route built on `queueResponse`.

`QueueOutcome` gains `jobIds`, parallel to `queued` and in the same order.
`queueResponse` emits it, plus a bare `jobId` when exactly one job started so
the single-slug case reads like every other job-starting route. Both ADDITIVE:
`queued` is the documented body shape of two public routes and a UI reads it as
slugs, so it keeps its meaning and its spelling.

The script reads `jobIds` FIRST — a bulk response now carries both, and
checking `jobId` first would follow one job out of five.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/api/ops/_lib.ts | 12++++++++++++
Meditor/app/channels/bulkStorageActions.ts | 1+
Meditor/app/channels/lib/queueForSlugs.ts | 14+++++++++++++-
Meditor/e2e/ops-api.spec.ts | 12++++++++++++
Mscripts/archilyzer-ops.mjs | 17++++++++++++-----
5 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/editor/app/api/ops/_lib.ts b/editor/app/api/ops/_lib.ts @@ -200,10 +200,22 @@ export function okResponse( // A bulk fan-out's { queued, skipped }. A skip is not a failure — the caller // gets both lists and decides, exactly as the bulk bar in the UI does. +// +// IT ALSO RETURNS THE JOB IDS, and that was not a nicety: without them a +// job-starting fan-out was indistinguishable from an action that started +// nothing, so `pnpm ops relocate --wait` returned 0 the moment the response +// arrived and reported success about a copy that had not begun. +// +// `jobIds` is parallel to `queued`; `jobId` is present only when exactly one +// job was started, so the single-slug case reads like every other job-starting +// route (`jobResponse`). Both are ADDITIVE — `queued` and `skipped` keep their +// meaning and their spelling. export function queueResponse(outcome: QueueOutcome): NextResponse { return NextResponse.json({ ok: true, queued: outcome.queued, skipped: outcome.skipped, + jobIds: outcome.jobIds, + ...(outcome.jobIds.length === 1 ? { jobId: outcome.jobIds[0] } : {}), }); } diff --git a/editor/app/channels/bulkStorageActions.ts b/editor/app/channels/bulkStorageActions.ts @@ -75,6 +75,7 @@ export async function bulkRelocateChannelMediaAction( ); const refuseAll = (reason: string): BulkRelocateResult => ({ queued: [], + jobIds: [], skipped: slugs.map((slug) => ({ slug, reason })), }); if ("error" in resolved) return refuseAll(resolved.error); diff --git a/editor/app/channels/lib/queueForSlugs.ts b/editor/app/channels/lib/queueForSlugs.ts @@ -10,6 +10,16 @@ import type { StreamActionResult } from "yt-dlp-transcript-common/jobs/streamCom export type QueueOutcome = { queued: string[]; skipped: { slug: string; reason: string }[]; + // THE JOB IDS, IN THE SAME ORDER AS `queued`. Added because a fan-out's + // response carried no way to FOLLOW what it started: `pnpm ops relocate + // --wait` read `{ok, queued:[slug], skipped:[]}`, found no `jobId`, decided + // nothing had been started and returned 0 immediately — reporting success + // about a 130 GB copy that had not begun. + // + // A separate array rather than turning `queued` into objects: `queued` is the + // documented body shape of two public routes and a UI reads it as slugs. + // Additive, never renamed. + jobIds: string[]; }; export async function queueForSlugs( @@ -21,6 +31,7 @@ export async function queueForSlugs( }, ): Promise<QueueOutcome> { const queued: string[] = []; + const jobIds: string[] = []; const skipped: { slug: string; reason: string }[] = []; for (const slug of slugs) { const reason = opts.skip ? await opts.skip(slug) : null; @@ -34,10 +45,11 @@ export async function queueForSlugs( continue; } queued.push(slug); + jobIds.push(result.jobId); // Nobody will read the stream here — cancel it so the buffered chunks // can be GC'd. The job keeps running and writes to its log file via // runManagedFunction's onLog regardless. void result.stream.cancel(); } - return { queued, skipped }; + return { queued, jobIds, skipped }; } diff --git a/editor/e2e/ops-api.spec.ts b/editor/e2e/ops-api.spec.ts @@ -35,6 +35,7 @@ type OpsResponse = { error?: string; jobId?: string; queued?: string[]; + jobIds?: string[]; skipped?: { slug: string; reason: string }[]; }; @@ -399,6 +400,11 @@ test("refresh-report regenerates snapshot.json", async ({ request }) => { expect([...(all.body.queued ?? []), ...(all.body.skipped ?? []).map((s) => s.slug)]).toContain( SLUG, ); + // ONE JOB ID PER QUEUED SLUG, in the same order. This is what `--wait` + // follows; without it a fan-out response was indistinguishable from an + // action that started nothing. + expect(all.body.jobIds?.length).toBe(all.body.queued?.length); + for (const id of all.body.jobIds ?? []) expect(id).toBeTruthy(); const both = await ops(request, "refresh-report", { slug: SLUG, all: true }); expect(both.status).toBe(400); @@ -438,6 +444,12 @@ test("relocate refuses a busy channel with the sentence the panel shows", async // A SKIP IS NOT A FAILURE — the bulk bar renders both numbers, and so does // this. The reason is channelMediaBusyReason's, word for word. expect(refused.body.queued).toEqual([]); + // AND NO JOB IDS, which is the half `--wait` reads: a fan-out that started + // nothing must be distinguishable from one that started something. Before + // `jobIds` existed a relocate response carried no id at all, so + // `pnpm ops relocate --wait` found none, concluded nothing had been started + // and returned 0 — reporting success about a 130 GB copy that had not begun. + expect(refused.body.jobIds).toEqual([]); expect(refused.body.skipped?.[0]?.slug).toBe(SLUG); expect(refused.body.skipped?.[0]?.reason).toMatch( /running\/queued job\(s\) for this channel/, diff --git a/scripts/archilyzer-ops.mjs b/scripts/archilyzer-ops.mjs @@ -216,11 +216,18 @@ async function main() { console.log(JSON.stringify(payload, null, 2)); if (!res.ok || payload.ok === false) return 1; if (!parsed.wait) return 0; - const jobIds = payload.jobId - ? [payload.jobId] - : Array.isArray(payload.jobs) - ? payload.jobs.map((j) => j.jobId) - : []; + // `jobIds` FIRST: a bulk fan-out (relocate, relocate-back) returns an array + // and also a single `jobId` when it started exactly one, so reading `jobId` + // first would follow one job out of five. Before `jobIds` existed those + // routes carried no id at all and --wait returned immediately, reporting + // success about a copy that had not begun. + const jobIds = Array.isArray(payload.jobIds) + ? payload.jobIds + : payload.jobId + ? [payload.jobId] + : Array.isArray(payload.jobs) + ? payload.jobs.map((j) => j.jobId) + : []; if (jobIds.length === 0) { // Not a job-starting action (or it queued nothing). --wait is satisfied. return 0;