Archilyzer · Source

archilyzer

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

commit 9dc0cec1a1330088c0ad6f306761df3aa7745123
parent 18e0407b7ad06de5ae93627150e43e82b2273ee3
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 25 Aug 2026 23:52:03 -0400

operations/<id>: resolve the runner and the sweep lane from the registry

Four hardcoded guesses decided what an operation page rendered, none of them
consulting the descriptor:

  runnerKind   = id === "transcription" || id === "download" ? id : null
  laneJobKinds = JOB_KINDS_BY_LANE[id === "digest" ? "digest" : "backfill"]
  sweepLaneIdFor(id) -> "backfill" for anything not digest
  railStates: every band with no state of its own took the backfill lane's

All four were right only by coincidence of today's registry — two runners, two
sweep lanes, nothing else. A registered `transcode` (external, no runner) would
have rendered the BACKFILL SWEEP'S CONSOLE under a "Transcode" heading: its
band, its plan, its scope, and a live Start button that arms a corpus-scale GPU
commitment. A console for the wrong lane is worse than no console, because
every control on it works.

`dispatch` alone cannot resolve it: download and transcription are `external`
WITH a runner, transcode would be `external` with none. So the registry states
it. `ExternalOperation`/`OperationDescriptor` gain `runner?: AutoQueueKind`,
imported as `import type` — erased at compile time, so it cannot create a cycle
whatever jobs/autoQueueState.ts imports.

`sweepLaneIdFor` moves to app/operations/lanes.ts (where SweepLaneId and the
queue keys already live), takes the DESCRIPTOR, reads `lane.queueKey`, and
returns null for an operation neither sweep runs. Null is a real answer:
OperationDetail renders it as a "no console here" panel — the band, the
dependency chain, the cost basis, and a sentence pointing at `op.hint` for what
does dispatch it. It emits no data-lane, so no test can scope lane controls to
a page that has none.

railStates' band loop asks THE PAYLOAD rather than the registry:
`lane.operations` is the live membership the server already computed, so it
cannot drift the way a second registry read could. A band in neither lane gets
no entry, and the existing contract supplies the rest — no lane reads "Off",
never a borrowed "Idle".

Registering `transcode` itself is not this commit.

tsc clean; common 822/822 (one new test: the two runners declare `runner`,
every sweep-dispatched kind leaves it unset).

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

Diffstat:
Mcommon/lib/backfillKinds.test.ts | 17+++++++++++++++++
Mcommon/lib/backfillKinds.ts | 15+++++++++++++++
Meditor/app/operations/[id]/page.tsx | 28+++++++++++++++++++++-------
Meditor/app/operations/components/OperationDetail.tsx | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Meditor/app/operations/components/railStates.ts | 45++++++++++++++++++++++++++++++++-------------
Meditor/app/operations/lanes.ts | 23+++++++++++++++++++++++
6 files changed, 192 insertions(+), 26 deletions(-)

diff --git a/common/lib/backfillKinds.test.ts b/common/lib/backfillKinds.test.ts @@ -1197,6 +1197,23 @@ test("the catalog covers every operation, dispatched here or not", () => { } }); +test("`runner` names the auto-queue runner, and only for the two that have one", () => { + // The console reads this instead of asking whether the id happens to be + // "download" or "transcription". `dispatch` cannot answer it: both of those + // are `external` WITH a runner, and a future `transcode` would be `external` + // with none — so an id-shaped guess would hand transcode the transcription + // runner's controls. + const runners = new Map(operationCatalog().map((o) => [o.id, o.runner])); + assert.equal(runners.get("download"), "download"); + assert.equal(runners.get("transcription"), "transcription"); + // Everything the sweep dispatches must leave it unset — a backfill kind with + // a runner would render a runner console over a lane no runner feeds. + for (const op of operationCatalog()) { + if (op.id === "download" || op.id === "transcription") continue; + assert.equal(op.runner, undefined, `${op.id} declares a runner`); + } +}); + test("every catalogued operation declares a group and a cost basis", () => { // Both are read unconditionally by the UI — the transit line groups stations // by `group`, and every armed operation prints `costBasis` beside its diff --git a/common/lib/backfillKinds.ts b/common/lib/backfillKinds.ts @@ -69,6 +69,10 @@ import { DIGEST_REMOTE_QUEUE, TRANSCRIPTION_QUEUE, } from "./queueKeys"; +// TYPE-ONLY, and that is what keeps this safe: the import is erased at compile +// time, so naming the runner union here cannot create a cycle no matter what +// jobs/autoQueueState.ts imports. +import type { AutoQueueKind } from "../jobs/autoQueueState"; import { DIGEST_FILENAME, isDigestSectionKind, @@ -1195,6 +1199,12 @@ export type ExternalOperation = { lane: BackfillLane; dependsOn?: readonly string[]; dispatch: "external"; + // The auto-queue runner that dispatches this, when one does. `dispatch` alone + // cannot answer it: download and transcription are `external` WITH a runner, + // and a future `transcode` would be `external` with none. A console that + // guessed from the id would hand transcode the transcription runner's + // controls — a live Start button over the wrong lane. + runner?: AutoQueueKind; }; export const EXTERNAL_OPERATIONS: readonly ExternalOperation[] = [ @@ -1210,6 +1220,7 @@ export const EXTERNAL_OPERATIONS: readonly ExternalOperation[] = [ // the dependency graph, not dispatch. lane: { queueKey: "download:<platform>", contendsFor: "network" }, dispatch: "external", + runner: "download", }, { id: "transcription", @@ -1221,6 +1232,7 @@ export const EXTERNAL_OPERATIONS: readonly ExternalOperation[] = [ lane: { queueKey: TRANSCRIPTION_QUEUE, contendsFor: "gpu" }, dependsOn: ["download"], dispatch: "external", + runner: "transcription", }, ]; @@ -1236,6 +1248,9 @@ export type OperationDescriptor = { lane: BackfillLane; dependsOn?: readonly string[]; dispatch: BackfillDispatch; + // See ExternalOperation.runner. Absent for every backfill kind: those are + // dispatched by the sweep and the arbiter, not by a runner. + runner?: AutoQueueKind; }; export function operationCatalog(): OperationDescriptor[] { diff --git a/editor/app/operations/[id]/page.tsx b/editor/app/operations/[id]/page.tsx @@ -4,10 +4,14 @@ import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { listChannelConfigs } from "yt-dlp-transcript-common/controller/channels"; import { PLATFORM_VALUES } from "yt-dlp-transcript-common/lib/platform"; import { selectableBucketsForKind } from "yt-dlp-transcript-common/jobs/autoQueuePolicy"; -import { operationCatalog } from "yt-dlp-transcript-common/lib/backfillKinds"; +import { + operationCatalog, + operationLabel, +} from "yt-dlp-transcript-common/lib/backfillKinds"; import type { AutoQueueKind } from "yt-dlp-transcript-common/jobs/autoQueueState"; import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; import { buildAutoQueueStatusPayload } from "../status"; +import { sweepLaneIdFor, type SweepLaneId } from "../lanes"; import { OperationDetail } from "../components/OperationDetail"; export const dynamic = "force-dynamic"; @@ -27,7 +31,7 @@ const BUCKETS_BY_KIND = { // // Keyed by LANE rather than by operation, because that is the truth: one job // on the shared queue is doing whichever kinds the sweep armed. -const JOB_KINDS_BY_LANE: Record<string, readonly string[]> = { +const JOB_KINDS_BY_LANE: Record<SweepLaneId, readonly string[]> = { digest: [ "digest-sweep", "digest-channel-local", @@ -63,8 +67,14 @@ export default async function OperationPage({ const op = descriptorFor(id); if (!op) notFound(); - const runnerKind: AutoQueueKind | null = - id === "transcription" || id === "download" ? id : null; + // OFF THE DESCRIPTOR, never off the id. `dispatch` alone cannot answer this — + // download and transcription are `external` WITH a runner, and a future + // `transcode` would be `external` with none — so the registry states it and + // this reads it. + const runnerKind: AutoQueueKind | null = op.runner ?? null; + // Likewise for the sweep lane. Null for an operation neither sweep dispatches; + // OperationDetail renders that as "no console here" rather than borrowing one. + const laneId = sweepLaneIdFor(op); const [initial, channels] = await Promise.all([ buildAutoQueueStatusPayload(), @@ -75,9 +85,8 @@ export default async function OperationPage({ name: c.config.name ?? null, })); - const laneJobKinds = runnerKind - ? [] - : (JOB_KINDS_BY_LANE[id === "digest" ? "digest" : "backfill"] ?? []); + const laneJobKinds = + runnerKind || !laneId ? [] : JOB_KINDS_BY_LANE[laneId]; const activeJobs = laneJobKinds.length === 0 ? [] @@ -116,6 +125,11 @@ export default async function OperationPage({ bucketsByKind={BUCKETS_BY_KIND} activeJobs={activeJobs} runnerKind={runnerKind} + laneId={laneId} + dependsOn={[...(op.dependsOn ?? [])].map((depId) => ({ + id: depId, + label: operationLabel(depId), + }))} /> </div> ); diff --git a/editor/app/operations/components/OperationDetail.tsx b/editor/app/operations/components/OperationDetail.tsx @@ -2,11 +2,12 @@ import type { AutoQueueKind } from "yt-dlp-transcript-common/jobs/autoQueueState"; import type { AutoQueueStatusPayload } from "../status"; +import type { SweepLaneId } from "../lanes"; import { HowPriorityWorks } from "./HowPriorityWorks"; import { OperationRail } from "./OperationRail"; import { RunnerOperationView } from "./RunnerOperationView"; import { SweepLane } from "./SweepLane"; -import { railStates, sweepLaneIdFor } from "./railStates"; +import { railStates } from "./railStates"; import { useHydrated, useOperationsStatus } from "./useOperationsStatus"; import { type Channel } from "./dispatch"; import { StateBand } from "../../components/pipelines/StateBand"; @@ -30,6 +31,8 @@ export function OperationDetail({ bucketsByKind, activeJobs, runnerKind, + laneId, + dependsOn, }: { id: string; initial: AutoQueueStatusPayload; @@ -40,10 +43,17 @@ export function OperationDetail({ // all (RunningJobsList returns null), so a quiet page carries no second // in-flight list beside the lane's own. activeJobs: RunningJobsListItem[]; - // Set for the two operations a RUNNER dispatches; null for everything the - // registry sweeps. Resolved on the server against operationCatalog() so this - // component never re-decides which is which. + // Set for the operations a RUNNER dispatches; null for everything else. + // Resolved on the server FROM THE DESCRIPTOR (`op.runner`) so this component + // never re-decides which is which — and so an operation the registry gains + // gets the right answer without a branch here. runnerKind: AutoQueueKind | null; + // The sweep lane that dispatches this, or null for one neither sweep runs. + // Also resolved on the server, off the descriptor's queue key. + laneId: SweepLaneId | null; + // This operation's declared inputs, labelled. Only rendered when there is no + // console — where it is most of what the page has to say. + dependsOn: { id: string; label: string }[]; }) { const { data, refresh } = useOperationsStatus(initial); @@ -68,13 +78,16 @@ export function OperationDetail({ onRefresh={refresh} /> </> - ) : ( + ) : laneId ? ( <SweepOperationView id={id} + laneId={laneId} data={data} activeJobs={activeJobs} onRefresh={refresh} /> + ) : ( + <NoConsoleView id={id} data={data} dependsOn={dependsOn} /> )} </div> ); @@ -90,17 +103,18 @@ export function OperationDetail({ // lane holding three operations has no single total that means anything. function SweepOperationView({ id, + laneId, data, activeJobs, onRefresh, }: { id: string; + laneId: SweepLaneId; data: AutoQueueStatusPayload; activeJobs: RunningJobsListItem[]; onRefresh: () => Promise<void>; }) { const hydrated = useHydrated(); - const laneId = sweepLaneIdFor(id); const lane = data.lanes[laneId]; const band = data.lanes.bands.find((b) => b.id === id) ?? null; // The other operations this lane would dispatch, named. Not a count: "shares @@ -155,6 +169,70 @@ function SweepOperationView({ ); } +// AN OPERATION WITH NO CONSOLE HERE — registered, real, and dispatched by +// nothing this page can drive. +// +// The page it replaces was the reason this whole step exists. Before it, an +// operation with no runner and no sweep lane fell through to the backfill +// lane's console: its band, its plan, its scope, and a live START button that +// would have armed THE BACKFILL SWEEP — a corpus-scale GPU commitment — under +// this operation's heading. A console for the wrong lane is worse than none, +// because every control on it works. +// +// So this says the four true things instead: what state the corpus is in (the +// band), what has to happen first (dependsOn), what one unit costs, and — in a +// sentence taken from the descriptor's own hint — what does dispatch it. That +// last one is why `hint` is worth keeping honest: it is the whole answer here. +function NoConsoleView({ + id, + data, + dependsOn, +}: { + id: string; + data: AutoQueueStatusPayload; + dependsOn: { id: string; label: string }[]; +}) { + const hydrated = useHydrated(); + const band = data.lanes.bands.find((b) => b.id === id) ?? null; + + return ( + <section + // NO data-lane, deliberately: there is no lane, and emitting one would + // let a test scope lane controls to a page that has none. + data-operation={id} + data-no-console="true" + data-hydrated={hydrated ? "true" : undefined} + className="flex flex-col gap-4" + > + {band && ( + <div className="flex flex-col gap-2 rounded-lg border border-border bg-card px-4 py-3"> + <StateBand band={band} size="rail" /> + </div> + )} + + <div className="flex flex-col gap-2 rounded-lg border border-border bg-card px-4 py-3"> + <h2 className="font-display text-sm font-semibold tracking-tight"> + Not dispatched from here + </h2> + <p className="text-sm text-muted-foreground"> + Neither auto-queue runner nor either sweep lane runs this operation, + so there is nothing on this page to start or hold. The figures above + are the corpus state; see the description at the top of the page for + what does dispatch it. + </p> + {dependsOn.length > 0 && ( + <p className="text-sm text-muted-foreground"> + Needs{" "} + {joinLabels(dependsOn.map((d) => d.label))} first — a video without + that is counted under &ldquo;needs re-acquiring&rdquo;, not under + work that is ready. + </p> + )} + </div> + </section> + ); +} + // "A", "A and B", "A, B and C". Written out because the sentence it lands in is // the one that keeps an operator from reading a per-operation page as a // per-operation switch. diff --git a/editor/app/operations/components/railStates.ts b/editor/app/operations/components/railStates.ts @@ -1,5 +1,5 @@ import type { AutoQueueStatusPayload } from "../status"; -import type { SweepLaneStatus } from "../lanes"; +import type { SweepLaneId, SweepLaneStatus } from "../lanes"; import { deriveLaneState } from "../../components/lanes/laneState"; import type { RailLaneState } from "./OperationRail"; import { idleReasonText } from "./dispatch"; @@ -57,20 +57,39 @@ export function railStates( }); out.digest = sweepState(data.lanes.digest); - // Every OTHER catalog operation is dispatched by the one backfill lane, so it - // takes that lane's state — including the ones with no band of their own. - const backfill = sweepState(data.lanes.backfill); + + // Each remaining band takes the state of the lane THAT BAND'S OPERATION + // actually rides — asked of the payload, not assumed from the id. + // + // This loop used to hand every stateless band the backfill lane's state, on + // the same reasoning the old id-shaped sweepLaneIdFor used: everything that + // is not digest is backfill. Both were true only because those are the only + // two lanes registered TODAY. An operation on a third queue would still get a + // band (the rail is built from allBackfillKinds — every switched-on kind, + // whatever its queue) and would then have advertised a hold belonging to a + // lane that would never dispatch it. + // + // `lane.operations` is the live membership the server already computed, so + // this cannot drift from it the way a second registry read could. A band in + // NEITHER lane gets no entry at all, and railStates' contract above supplies + // the rest: no lane means "Off", never a borrowed "Idle". + const laneStates: Record<SweepLaneId, RailLaneState> = { + digest: out.digest, + backfill: sweepState(data.lanes.backfill), + }; + const laneIdOf = (operationId: string): SweepLaneId | null => { + for (const laneId of ["digest", "backfill"] as const) { + if (data.lanes[laneId].operations.some((op) => op.id === operationId)) { + return laneId; + } + } + return null; + }; for (const band of data.lanes.bands) { if (out[band.id]) continue; - out[band.id] = backfill; + const laneId = laneIdOf(band.id); + if (!laneId) continue; + out[band.id] = laneStates[laneId]; } return out; } - -// The lane that dispatches an operation, in the two ids the payload is keyed -// by. Digest has its own queue; everything else the registry dispatches rides -// the shared backfill lane. Stated once, because the page that renders a lane -// and the page that names it must not answer this differently. -export function sweepLaneIdFor(operationId: string): "digest" | "backfill" { - return operationId === "digest" ? "digest" : "backfill"; -} diff --git a/editor/app/operations/lanes.ts b/editor/app/operations/lanes.ts @@ -9,6 +9,7 @@ import { operationLabel, operationsActionLabel, operationsGroupLabel, + type OperationDescriptor, } from "yt-dlp-transcript-common/lib/backfillKinds"; import { buildSweepChannelCounts } from "yt-dlp-transcript-common/controller/sweepPreview"; import { loadSweepDates } from "yt-dlp-transcript-common/controller/sweepRecency"; @@ -49,6 +50,28 @@ import { export type SweepLaneId = "digest" | "backfill"; +// The sweep lane that dispatches an operation — or NULL for one neither sweep +// runs. Takes the DESCRIPTOR, and reads its queue key. +// +// The id-shaped version of this ("digest" ? digest : backfill) answered +// "backfill" for every operation that was not digest, including ones no sweep +// dispatches at all. It was right only by coincidence of today's registry: a +// registered `transcode` — external, no runner — would have rendered the +// backfill sweep's whole console, with a live Start button ARMING THE BACKFILL +// SWEEP, under a "Transcode" heading. The queue key is what the arbiter +// actually dispatches on, so it is what this asks. +// +// Null is a real answer, not an error. A caller must render it as "no console +// here" rather than falling back to a lane. +export function sweepLaneIdFor( + op: Pick<OperationDescriptor, "lane"> | null | undefined, +): SweepLaneId | null { + const key = op?.lane.queueKey; + if (key === DIGEST_LOCAL_QUEUE || key === DIGEST_REMOTE_QUEUE) return "digest"; + if (key === BACKFILL_QUEUE) return "backfill"; + return null; +} + export type SweepLaneStatus = { id: SweepLaneId; label: string;