commit d2efce8e88fec486342f663c2508eb93db0604b7
parent 14bc961a1f4b12b27f18684296c0d7bb7f944075
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Tue, 8 Sep 2026 01:01:23 -0400
common+editor: the runner draws a lane, not a list of buckets
buildChannelWork projects each bucket lane's work list under the id of the
operation it dispatches — snapshot.backfill[op].ids, falling back to the same
bucketLaneWorkIds fold the generator uses, because no live snapshot is
regenerated by this slice. It lands in the BUCKET claim space, so a video an
explicit retry-bucket leaf claimed is still not claimed twice by a catch-all;
defaultDrawsForPolicy keeps the opt-in auto-caption buckets at the tail.
EXTERNAL_BAND_IDS comes off EXTERNAL_OPERATIONS instead of being a hand-written
pair. The rail keeps its own fold and says why: the entry is the DISPATCH list
(transcription's includes the 1,873-video retry bucket, the band's does not).
Numbers: the phase1-numbers diff over the live corpus is empty.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 65 insertions(+), 14 deletions(-)
diff --git a/common/controller/autoRunner.ts b/common/controller/autoRunner.ts
@@ -25,10 +25,16 @@ import {
buildPendingByLeaf,
flattenLeaves,
selectNextWork,
- defaultBucketsForPolicy,
+ bucketIdsFrom,
+ bucketLaneWorkIds,
+ defaultDrawsForPolicy,
selectableBucketsForKind,
} from "../jobs/autoQueuePolicy";
-import { operationsForLane, type Operation } from "../lib/operations";
+import {
+ bucketLaneOperationId,
+ operationsForLane,
+ type Operation,
+} from "../lib/operations";
import {
cheapestComparator,
classifyOperationUnit,
@@ -327,15 +333,32 @@ async function buildChannelWork(
// leaf (and no policy switch) asks for them, because buildPendingByLeaf
// only walks the buckets its `defaultBuckets` / `match.bucket` name.
for (const name of selectableBucketsForKind(kind)) {
- // `undownloadedIds` lives at the snapshot top level; every other bucket
- // is under snap.buckets.
- const ids =
- name === "undownloadedIds"
- ? (snap.undownloadedIds ?? [])
- : ((snap.buckets as Record<string, string[]>)?.[name] ?? []);
+ const ids = [...bucketIdsFrom(snap, name)];
buckets[name] = ids;
for (const id of ids) if (!owner.has(id)) owner.set(id, slug);
}
+ // THE LANE'S OWN WORK LIST, under the id of the operation it dispatches —
+ // slice 1.5. A bucket-less leaf draws THIS rather than walking the default
+ // buckets itself (see defaultDrawsForPolicy), so all four lanes now answer
+ // "what does this lane have to do" out of `snapshot.backfill[op].ids`.
+ //
+ // THE FALLBACK IS THE MIGRATION. No live snapshot is regenerated by the
+ // slice that added the entry, so a channel reads its work list through
+ // bucketLaneWorkIds until its next regen — the same fold
+ // generateChannelSnapshot writes, so the two cannot disagree.
+ //
+ // Projected into `buckets`, NOT into `operations`, and that is the whole
+ // reason nothing moves: the bucket claim space is `\0id`, so a video an
+ // explicit `failedListed` leaf already claimed is not claimed a second time
+ // by a catch-all leaf drawing this list. The operation claim space exists to
+ // keep diarization and attribution apart on ONE video, and a bucket lane has
+ // no second operation to keep apart from.
+ const laneWorkList = bucketLaneOperationId(kind);
+ if (laneWorkList) {
+ const ids = snap.backfill?.[laneWorkList]?.ids ?? bucketLaneWorkIds(kind, snap);
+ buckets[laneWorkList] = ids;
+ for (const id of ids) if (!owner.has(id)) owner.set(id, slug);
+ }
let ops: Record<string, string[]> | undefined;
if (operations.length > 0) {
ops = {};
@@ -574,7 +597,7 @@ export async function computeLeafPending(
const pending = buildPendingByLeaf(
policy.root,
channels,
- defaultBucketsForPolicy(kind, policy),
+ defaultDrawsForPolicy(kind, policy, bucketLaneOperationId(kind)),
{ ...(compare ? { compare } : {}), defaultOperations: laneOperations },
);
// A video already in flight is not "next up" — drop the live runner's set
@@ -1083,7 +1106,7 @@ async function runLoop(
const pending = buildPendingByLeaf(
policy.root,
channels,
- defaultBucketsForPolicy(kind, policy),
+ defaultDrawsForPolicy(kind, policy, bucketLaneOperationId(kind)),
{ ...(compare ? { compare } : {}), defaultOperations: laneOperations },
);
// A video already in flight is dropped whatever lane this is: two units on
diff --git a/editor/app/components/pipelines/buildBands.ts b/editor/app/components/pipelines/buildBands.ts
@@ -1,6 +1,7 @@
import type { ChannelSnapshot } from "yt-dlp-transcript-common/controller/channelSnapshot";
import { excludedDownloadIdSet } from "yt-dlp-transcript-common/controller/channelSnapshot";
import {
+ EXTERNAL_OPERATIONS,
operationCostBasis,
operationLabel,
presentOperationWork,
@@ -88,13 +89,31 @@ export type BuildOperationBandsInput = {
// blocked behind diarization). Leaving transcription and download off it would
// remove exactly the two lanes whose state explains the other four.
//
-// Their numbers do NOT come from Operation.state() — they have no entry,
-// because EXTERNAL_OPERATIONS registers them for the dependency graph and not
-// for dispatch. They come from `totals` and the buckets, using the SAME
+// Their numbers do NOT come from Operation.state() — they have no registry
+// entry, because EXTERNAL_OPERATIONS registers them for the dependency graph
+// and not for dispatch. They come from `totals` and the buckets, using the SAME
// definitions the channel transit line already uses for its Download and
// Transcribe stations, so a corpus figure and a channel figure cannot disagree
// about what "downloaded" means.
//
+// SLICE 1.5 GAVE THEM A SNAPSHOT ENTRY, AND THIS STILL DOES NOT READ IT.
+// `snapshot.backfill.download` and `.transcription` are the DISPATCH work list —
+// what the runner would hand out — and that is a different set from what these
+// five numbers mean, in two places that both matter:
+//
+// * transcription's `reachable` here is downloadedNoTranscript ALONE. The
+// lane's work list also carries `failedListed`, the retry bucket, which is
+// 1,873 videos corpus-wide against 881 — reading the entry would nearly
+// quadruple a rendered figure.
+// * this band's `blocked` is "no audio yet", which the entry calls
+// `missingInput`, and its `eligible`/`present` are the playlist and
+// `totals.downloaded`/`totals.transcribed` — coverage measures the entry
+// states rather than counts.
+//
+// So the entry and the band are two honest answers to two different questions,
+// and the rail keeps its own. What DID stop being a special case is the id list
+// below: it is the catalog's own, not a hand-written pair.
+//
// Sync is catalogued beside them and still gets NO band, here or anywhere: its
// populations are channels, not videos (`scope: "channel"`), so every one of a
// band's five numbers would be a category error and the rail would draw
@@ -160,7 +179,16 @@ function addExternalBands(
// The rail, left to right. Download and transcription lead because everything
// else depends on them; digest and the backfill kinds follow in registry order.
-export const EXTERNAL_BAND_IDS = ["download", "transcription"] as const;
+//
+// OFF THE CATALOG, not a literal pair. EXTERNAL_OPERATIONS is the declaration of
+// exactly this set — the media-derived pipelines this system counts and does not
+// dispatch through the registry — and sync is deliberately not in it (its
+// populations are channels, not videos). A third external pipeline would join
+// the rail by being declared, the way pauseLaneFor and bucketLaneOperationId
+// already read that same field.
+export const EXTERNAL_BAND_IDS: readonly string[] = EXTERNAL_OPERATIONS.map(
+ (op) => op.id,
+);
export function buildOperationBands({
snapshots,