Merge branch 'deai2'

Co-Authored-By: Claude Fable 5.1 <[email protected]>

# Conflicts:
#	apps/server/src/modules/acquisition-organize/services/organize.ts
#	apps/web/app/routes/console/sites.$siteId.tsx
This commit is contained in:
root committed 2026-09-29 21:26:16 +08:00
commit 12bc4664b6
247 files changed
+2658 -2148

No files matched your search

+1 -1
View File
@@ -158,7 +158,7 @@ describe.skipIf(!hasPg)("bootstrap + worker(真实 PG)", () => {
decisionId,
kind: "identity_unresolved",
severity: "info",
title: "待识别",
title: "未识别",
});
});
const deadline = Date.now() + 20_000;
+1 -1
View File
@@ -93,7 +93,7 @@ const optionsScript = (goal: string): FakeScript => ({
},
});
// 审批:两个目标各用一个版本目标(拒绝与批准互不影响);ID 在 beforeAll 里填
// 审批:两个目标各用一个收录版本(拒绝与批准互不影响);ID 在 beforeAll 里填
const T: Record<"approve" | "reject", { slotId: string; releaseId: string }> = {
approve: { slotId: ZERO, releaseId: ZERO },
reject: { slotId: ZERO, releaseId: ZERO },
@@ -15,7 +15,7 @@ type Any = any;
const hasPg = await pgAvailable();
const HASH = "ab".repeat(20);
const SURVEY = "盘点订阅、待处理与下载";
const LOOP = (at: string) => `巡检库存(${at}):逐个作品查看版本目标`;
const LOOP = (at: string) => `巡检库存(${at}):逐个作品查看收录版本`;
describe.skipIf(!hasPg)("E15 Agent 质量(fake 模型 + 真实能力)", () => {
const scripts: FakeScript[] = [];
+1 -1
View File
@@ -394,7 +394,7 @@ describe.skipIf(!hasPg)("Pi 上游(真实 PG)", () => {
const r4 = await newRun(null);
const d4 = await drive(env, h.ctx.capabilities, r4, new AbortController().signal);
expect(d4).toMatchObject({ status: "failed" });
expect(d4.message).toMatch(/未配置 Agent 上游/);
expect(d4.message).toMatch(/未配置模型服务/);
await setDefaultUpstream(h.ctx, fake.id);
});
});
+2 -2
View File
@@ -154,7 +154,7 @@ describe.skipIf(!hasPg)("规则组 API(真实 PG)", () => {
expect(draft.body.data.items[0]).toMatchObject({ passed: true, pri: 100 });
});
it("评估接入:版本目标引用规则组 → 硬排除拒绝、pri 进候选评估 trace;被引用的组不能删", async () => {
it("评估接入:收录版本引用规则组 → 硬排除拒绝、pri 进候选评估 trace;被引用的组不能删", async () => {
const e = await seedEntity(h.db, { kind: "movie", title: "规则片", year: 2024 });
const slot = await seedSlot(h.db, { entityId: e });
const g = await h.api("POST", "/rules", { name: "片源", ruleString: "4K > 1080P", hardExcludes: ["REMUX"] });
@@ -285,7 +285,7 @@ describe.skipIf(!hasPg)("规则组 API(真实 PG)", () => {
expect(plan.evaluation).toMatchObject({ pri: 100 });
});
it("默认链:订阅默认组有层时对未引用规则组的版本目标生效;PUT /rules/defaults 校验类型", async () => {
it("默认链:订阅默认组有层时对未引用规则组的收录版本生效;PUT /rules/defaults 校验类型", async () => {
const defs = (await h.api("GET", "/rules/defaults")).body.data;
const dflt = defs.acquisition[0];
expect((await h.api("PUT", "/rules/defaults", { ...defs, acquisition: defs.upgrade })).status).toBe(400);
+3 -3
View File
@@ -293,7 +293,7 @@ describe.skipIf(!up)("全局搜索", () => {
expect(a.body.data.items[0]).toMatchObject({ siteId: sites.mteam, siteReleaseId: "912234" });
});
it("库存叠加:已识别资源 → resolved + 订阅/版本目标缺口;未识别但标题命中唯一作品别名 → alias", async () => {
it("库存叠加:已识别资源 → resolved + 订阅/收录版本缺口;未识别但标题命中唯一作品别名 → alias", async () => {
const entity = (
await db
.insertInto("media_entity")
@@ -346,7 +346,7 @@ describe.skipIf(!up)("全局搜索", () => {
const mt = items.find((i) => i.siteReleaseId === "990001");
expect(mt).toMatchObject({ mediaEntityId: entity, entityMatch: "alias", inventory: { state: "gap" } });
// 本地作品组:别名精确命中 + 版本目标状态摘要
// 本地作品组:别名精确命中 + 收录版本状态摘要
const all = await req("/search?q=沙丘2&scope=media");
const local = all.body.data.media.local.items;
expect(local[0]).toMatchObject({
@@ -512,7 +512,7 @@ describe.skipIf(!up)("全局搜索", () => {
expect(unresolved.status).toBe(412);
expect(unresolved.body.error.code).toBe("precondition_failed");
// 已识别但作品没有版本目标 → submitted=false + no_slot
// 已识别但作品没有收录版本 → submitted=false + no_slot
const other = (
await db
.insertInto("media_entity")
+1 -1
View File
@@ -167,7 +167,7 @@ describe.skipIf(!up)("两阶段搜索(SEARCH-2)", () => {
sites.c = await addSite("hdhome", "家园", null, true);
await db.updateTable("site").set({ enabled: false }).where("id", "=", sites.c).execute();
// 本地已建档的沙丘2(接受了 TMDB 693134),有一个未满足的版本目标
// 本地已建档的沙丘2(接受了 TMDB 693134),有一个未满足的收录版本
dune = (
await db
.insertInto("media_entity")
@@ -179,7 +179,7 @@ describe.skipIf(!pg)("DEC-1 惰性识别 + 待识别池", () => {
expect(ds).toHaveLength(1);
const d = ds[0]!;
expect(d.severity).toBe("warning");
expect(d.title).toMatch(/^来源「.+」的 3 条资源待确认身份:Twins$/);
expect(d.title).toMatch(/^来源「.+」的 3 条资源没识别出作品:Twins$/);
const ev = d.evidence as { count: number; releases: { sourceReleaseId: string }[] };
expect(ev.count).toBe(3);
expect(ev.releases.map((x) => x.sourceReleaseId).sort()).toEqual([...ids].sort());
@@ -228,7 +228,7 @@ describe.skipIf(!pg)("DEC-1 惰性识别 + 待识别池", () => {
expect(after.status).toBe("open");
expect((after.evidence as { count: number }).count).toBe(1);
expect(after.subject_id).toBe(pool[1]!.source_release_id);
expect(after.title).toMatch(/^待确认身份:Nightfall\.1995\./);
expect(after.title).toMatch(/^没识别出作品:Nightfall\.1995\./);
// 最后一条忽略 → 事项关闭
const last = await h.db
.selectFrom("identity_candidate")
@@ -336,7 +336,7 @@ describe.skipIf(!pg)("迁移 0047:存量 identity_unresolved 清理", () => {
.values({
kind: "identity_unresolved",
severity: "info",
title: `待识别:${title}`,
title: `未识别:${title}`,
subject_type: "source_release",
subject_id: r.id,
dedupe_key: `identity:${r.id}`,
@@ -26,7 +26,7 @@ import {
presetConditions,
} from "../../src/modules/acquisition-organize/services/subscriptions-params.ts";
// C1-A 订阅遗留收尾:包含 / 排除 → 订阅私有规则组(评估链纳入、随订阅删除)、MP 分辨率 / 来源 / 特效 → 版本目标、
// C1-A 订阅遗留收尾:包含 / 排除 → 订阅私有规则组(评估链纳入、随订阅删除)、MP 分辨率 / 来源 / 特效 → 收录版本、
// 保存路径经路径映射传给 qB、库根用于首次整理、下载器实例、重置进度、日历。
const hasPg = await pgAvailable();
@@ -182,7 +182,7 @@ describe.skipIf(!hasPg)("C1-A 订阅遗留收尾(真实 PG + mock qB)", () => {
expect(again[0]!.hard).toEqual({ exclude: ["CAM"] });
});
it("评估链纳入私有组:包含没命中 / 排除命中都拒绝;命中包含建计划;与版本目标自己的包含取 AND", async () => {
it("评估链纳入私有组:包含没命中 / 排除命中都拒绝;命中包含建计划;与收录版本自己的包含取 AND", async () => {
const e = await seedEntity(db(), { kind: "movie", title: "Filter Film", year: 2024 });
const c = await h.api("POST", "/subscriptions", {
mediaEntityId: e,
@@ -207,7 +207,7 @@ describe.skipIf(!hasPg)("C1-A 订阅遗留收尾(真实 PG + mock qB)", () => {
const ok = await release(e, "Filter.Film.2024.1080p.WEB-DL.CHS.x264-GRP");
const r3 = await evaluateAcquisition(h.d, { sourceReleaseId: ok });
expect(r3.plans, JSON.stringify(r3)).toHaveLength(1);
// 版本目标自己也要求 "GRP2":与私有组的 CHS 取 AND
// 收录版本自己也要求 "GRP2":与私有组的 CHS 取 AND
await db().updateTable("acquisition_plan").set({ status: "cancelled" }).where("id", "=", r3.plans[0]!).execute();
await db()
.updateTable("acquisition_slot")
@@ -223,7 +223,7 @@ describe.skipIf(!hasPg)("C1-A 订阅遗留收尾(真实 PG + mock qB)", () => {
expect((await evaluateAcquisition(h.d, { sourceReleaseId: both })).plans).toHaveLength(1);
});
it("MP 分辨率 / 来源 / 特效:新建时建默认版本目标承载预设;改预设覆盖同一字段,手改过的字段保留;无法识别 400", async () => {
it("MP 分辨率 / 来源 / 特效:新建时建默认收录版本承载预设;改预设覆盖同一字段,手改过的字段保留;无法识别 400", async () => {
const e = await seedEntity(db(), { kind: "series", title: "预设剧", year: 2025 });
const bad = await h.api("POST", "/subscriptions", {
mediaEntityId: e,
@@ -461,7 +461,7 @@ describe.skipIf(!hasPg)("C1-A 订阅遗留收尾(真实 PG + mock qB)", () => {
});
expect(c.status, JSON.stringify(c.body)).toBe(201);
const subId = c.body.data.id as string;
// E01 已入库(单版本目标:作品下资产都算)
// E01 已入库(单收录版本:作品下资产都算)
const a = await db()
.insertInto("media_asset")
.values({
@@ -264,7 +264,7 @@ describe.skipIf(!hasPg)("订阅重做(真实 PG)", () => {
expect((await h.api("GET", "/subscriptions/00000000-0000-0000-0000-000000000000/coverage")).status).toBe(404);
});
it("覆盖矩阵:集范围裁剪;总集数未知列到已见最高集并标 suspected;电影按内容版本 × 版本目标", async () => {
it("覆盖矩阵:集范围裁剪;总集数未知列到已见最高集并标 suspected;电影按内容版本 × 收录版本", async () => {
const e = await seedEntity(db(), { kind: "series", title: "范围剧", year: 2025 });
await season(e, 1, null);
const e3 = await episode(e, 1, 3);
@@ -325,7 +325,7 @@ describe.skipIf(!hasPg)("订阅重做(真实 PG)", () => {
// 暂停
const e4 = await seedEntity(db(), { kind: "series", title: `${tag}丁`, year: 2022 });
const s4 = await sub(e4, { status: "paused" });
// 已完成(电影:唯一版本目标已入库)
// 已完成(电影:唯一收录版本已入库)
const e5 = await seedEntity(db(), { kind: "movie", title: `${tag}戊`, year: 2021 });
const a5 = await asset(e5);
const s5 = await sub(e5, { kind: "movie", mode: "once" });
@@ -584,7 +584,7 @@ describe.skipIf(!hasPg)("订阅重做(真实 PG)", () => {
expect(r.status, JSON.stringify(bad)).toBe(400);
}
// PATCH:autoFill 布尔 → on/off;best_version → 版本目标升级策略
// PATCH:autoFill 布尔 → on/off;best_version → 收录版本升级策略
const sid = c.body.data.id;
const sl = await slot(e, sid, "main");
const u = await h.api("PATCH", `/subscriptions/${sid}`, {
@@ -611,7 +611,7 @@ describe.skipIf(!hasPg)("订阅重做(真实 PG)", () => {
).toEqual({ enabled: false, mode: "full_pack" });
});
it("暂停 / 恢复 / 重新订阅 / 批量 / 版本目标排序", async () => {
it("暂停 / 恢复 / 重新订阅 / 批量 / 收录版本排序", async () => {
const e = await seedEntity(db(), { kind: "movie", title: "动作片", year: 2024 });
const s = await sub(e, { kind: "movie", mode: "once" });
const a = await slot(e, s, "A", { position: 0 });
+2 -2
View File
@@ -211,7 +211,7 @@ describe.skipIf(!hasPg)("CACHE-2 存储(真实 PG)", () => {
(${done}, 1, 'run_started', ${old}), (${done}, 2, 'run_started', ${now}), (${live}, 1, 'run_started', ${old})`.execute(
h.db,
);
expect(await job("agent_event")(now)).toMatch(/Agent 事件 1、/);
expect(await job("agent_event")(now)).toMatch(/智能体事件 1、/);
expect(await count(sql<{ n: number }>`select count(*)::int as n from agent_run_event`)).toBe(2);
await sql`delete from audit_event`.execute(h.db);
@@ -250,7 +250,7 @@ describe.skipIf(!hasPg)("CACHE-2 存储(真实 PG)", () => {
await cand("r1", "unmatched", 31);
await cand("r2", "unmatched", 29);
await cand("r3", "ambiguous", 31);
expect(await job("identity_pool")(now)).toBe("归档 1 条待识别条目");
expect(await job("identity_pool")(now)).toBe("归档 1 条未识别条目");
const st = await sql<{ t: string; s: string }>`select sr.title as t, c.status as s from identity_candidate c
join source_release sr on sr.id = c.source_release_id order by sr.title`.execute(h.db);
expect(st.rows).toEqual([