commit bc891e659538c8459bb72d313d08077e19efed7a
parent d9c50207d699fd3fb9b13cdc7f0bb42f0314070a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 01:57:05 -0400
common: the digest channel job takes an id scope, as the backfill job does
`runDigestChannelJob` gains `ids` (intersected with disk by the batch, as
`runBackfillChannelJob`'s are), passed to countOperationWork and
runOperationBatch and stored in `spec.params`; `runOperationChannelJob`
passes `ids` to both runners. The editor's replay of a `digest-channel-*`
job forwards `ids` through `digestChannelAction`, so a replayed scoped run
never widens to the whole channel (the backfill replay's rule, now through
one `strings()` helper for both). No caller passes ids yet: `archilyzer run
digest <channel> <ids…>` is the first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/common/controller/operationJobs.ts b/common/controller/operationJobs.ts
@@ -169,6 +169,9 @@ export type DigestChannelJobOptions = {
order?: string;
limitCount?: number;
force?: boolean;
+ // Only these videos (intersected with disk by the batch), as on the backfill
+ // job. In `spec.params` so a replay stays scoped. Absent = the channel.
+ ids?: string[];
background?: boolean;
onStarted?: (jobId: string) => void;
onDone?: () => void;
@@ -220,6 +223,7 @@ export async function runDigestChannelJob(
order: opts.order,
limitCount: opts.limitCount,
force: opts.force,
+ ids: opts.ids,
},
},
fn: async (onLog, signal, setProgress, ctx) => {
@@ -229,6 +233,7 @@ export async function runDigestChannelJob(
const missing = (
await countOperationWork("digest", paths, channelSlug, {
digestLane: lane,
+ ids: opts.ids,
})
).reachable;
// The bar measures THIS RUN, from zero, and the batch reports its own
@@ -241,6 +246,7 @@ export async function runDigestChannelJob(
channelSlug,
paths,
digestLane: lane,
+ ids: opts.ids,
digestOrder: isDigestOrder(opts.order) ? opts.order : undefined,
// The local lane takes everything up to the long-tail cutoff; the
// metered lane exists for the tail above it. Passing no window (the
@@ -291,6 +297,8 @@ export type OperationChannelJobOptions = {
channelSlug: string;
// A catalog operation id — "digest", "diarization", "attribution-text", …
operation: string;
+ // Only these videos; absent = the whole channel.
+ ids?: string[];
queueKey?: string;
background?: boolean;
onStarted?: (jobId: string) => void;
@@ -330,6 +338,7 @@ export function runOperationChannelJob(
order: opts.digest?.order,
limitCount: opts.digest?.limitCount,
force: opts.digest?.force,
+ ids: opts.ids,
queueKey: opts.queueKey,
background: opts.background,
onStarted: opts.onStarted,
@@ -340,6 +349,7 @@ export function runOperationChannelJob(
paths,
channelSlug,
kindIds: [operation],
+ ids: opts.ids,
// RESERVE BY THE LANE'S OWN KEY, never invent one. This is what keeps digest
// and diarization overlapping instead of taking turns, which backfill.spec
// pins. laneForOperation is live (it asks the kind's laneFor), so a caller
diff --git a/editor/app/channels/[slug]/digestActions.ts b/editor/app/channels/[slug]/digestActions.ts
@@ -66,6 +66,9 @@ export async function digestChannelAction(
order?: string,
limitCount?: number,
force?: boolean,
+ // Only these videos — a replay of an ids-scoped run (`archilyzer run digest
+ // <channel> <ids…>`) must not widen to the channel.
+ ids?: string[],
): Promise<StreamActionResult> {
return runDigestChannelJob({
paths: getPaths(),
@@ -75,6 +78,7 @@ export async function digestChannelAction(
order,
limitCount,
force,
+ ids,
onDone: () => safeRevalidate([`/channels/${slug}`]),
});
}
diff --git a/editor/app/jobs/jobReplayRegistry.ts b/editor/app/jobs/jobReplayRegistry.ts
@@ -69,6 +69,9 @@ const bool = (v: unknown): boolean | undefined =>
typeof v === "boolean" ? v : undefined;
const num = (v: unknown): number | undefined =>
typeof v === "number" ? v : undefined;
+// An id scope: the strings of an array, or undefined (= the whole channel).
+const strings = (v: unknown): string[] | undefined =>
+ Array.isArray(v) ? v.filter((k): k is string => typeof k === "string") : undefined;
// Flag-style params + the captured queueKey for a spec.
function params(spec: JobSpec): {
@@ -129,6 +132,7 @@ export const JOB_REPLAY_HANDLERS: Record<string, ReplayHandler> = {
str(p.order),
num(p.limitCount),
bool(p.force),
+ strings(p.ids),
);
},
"digest-channel-remote": (spec) => {
@@ -140,6 +144,7 @@ export const JOB_REPLAY_HANDLERS: Record<string, ReplayHandler> = {
str(p.order),
num(p.limitCount),
bool(p.force),
+ strings(p.ids),
);
},
"whisper-all": (spec) => {
@@ -305,10 +310,7 @@ export const JOB_REPLAY_HANDLERS: Record<string, ReplayHandler> = {
const kindIds = Array.isArray(p.kindIds)
? p.kindIds.filter((k): k is string => typeof k === "string")
: undefined;
- const ids = Array.isArray(p.ids)
- ? p.ids.filter((k): k is string => typeof k === "string")
- : undefined;
- return backfillChannelAction(spec.slug, queueKey, kindIds, ids);
+ return backfillChannelAction(spec.slug, queueKey, kindIds, strings(p.ids));
},
"check-kept-deleted": (spec) => {
const { queueKey } = params(spec);