Archilyzer · Source

archilyzer

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

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:
Mcommon/controller/operationJobs.ts | 10++++++++++
Meditor/app/channels/[slug]/digestActions.ts | 4++++
Meditor/app/jobs/jobReplayRegistry.ts | 10++++++----
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);