Archilyzer · Source

archilyzer

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

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:
Mcommon/controller/autoRunner.ts | 43+++++++++++++++++++++++++++++++++----------
Meditor/app/components/pipelines/buildBands.ts | 36++++++++++++++++++++++++++++++++----
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,