Takeover identify (item 2): - Adopted tasks without source release / plan: on completion planImport parses main video path (file + parent + grandparent dirs, like MP MetaInfoPath) merged with the torrent name, then runs ID-2 matching via identifyParsed (local recall + TMDB; trigger "takeover" may query TMDB). auto -> bind/create entity, backfill download_task.media_entity_id and organize right away; no pool entry, no identity decision. - Failure / ambiguity: organize_identity decision with evidence and options: candidates, select entity (local / TMDB), provisional entity, dismiss. Decision closure (item 3): - decision-hooks registry: the decisions resolve route runs the owning module's executor before closing; errors keep the decision open. - organize_identity: backfill entity, freeze manual plan, enqueue run. - organize_blocked with target_exists: replace (verify + trash old) / keep both (rename "<name> (n)" with sidecars) / skip. - Successful organize closes both organize_identity and organize_blocked of the same download task (resolution manual_organize), also for manual path organizes matched to download tasks via path mappings. Co-Authored-By: Claude Opus 5.5 <[email protected]>
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
import type { DecisionRow } from "@umt/db";
|
|
import type { ServerContext } from "./types.ts";
|
|
|
|
// 待处理"处理"端点(sources-identity routes/decisions.ts)的执行钩子:其它模块按 dedupe_key 认领自己的事项,
|
|
// 在事项关闭前同步执行所选处理(失败抛错 → 事项保持打开,错误回给用户)。按 ctx 隔离(测试里多个装配互不影响)。
|
|
|
|
export interface DecisionResolveBody {
|
|
optionKey?: string | undefined;
|
|
dismiss?: boolean | undefined;
|
|
note?: string | undefined;
|
|
identity?:
|
|
| { mediaEntityId?: string | undefined; tmdb?: { type: "movie" | "tv"; id: string } | undefined }
|
|
| undefined;
|
|
}
|
|
|
|
export interface DecisionHookActor {
|
|
type: string;
|
|
id: string | null;
|
|
}
|
|
|
|
export interface DecisionHook {
|
|
/** 认领该事项(通常按 dedupe_key 前缀) */
|
|
match(d: DecisionRow): boolean;
|
|
/** 执行所选处理;返回写进 resolution.execution 的摘要 */
|
|
execute(d: DecisionRow, body: DecisionResolveBody, actor: DecisionHookActor): Promise<Record<string, unknown>>;
|
|
}
|
|
|
|
const hooks = new WeakMap<ServerContext, DecisionHook[]>();
|
|
|
|
export function registerDecisionHook(ctx: ServerContext, hook: DecisionHook): void {
|
|
const list = hooks.get(ctx) ?? [];
|
|
list.push(hook);
|
|
hooks.set(ctx, list);
|
|
}
|
|
|
|
export function decisionHookFor(ctx: ServerContext, d: DecisionRow): DecisionHook | undefined {
|
|
return hooks.get(ctx)?.find((h) => h.match(d));
|
|
}
|