feat: 业务节点发生命周期事件并调用链式钩子

- 订阅已建 / 已改 / 已完成 / 已取消,下载完成 / 失败 / 移除,整理结束(统一 organization.changed 终态),
  刮削完成 / 失败,作品进入关注,元数据已刷新(任务调用方),季集结构切换,站点不可达 / 恢复:
  都在调用方事务内写 outbox,带幂等键
- 钩子挂点:name.recognize(resolveRelease / identifyParsed)、organize.rename 与 organize.intercept(planImport)、
  resource.selection(订阅重评)、subscription.completion.check(completeSubscription)
- 测试:各节点事件 payload 与去重、整理改名 / 拦截 / 出错不影响整理、名称识别改写、资源选择、订阅完成拦截

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 19:27:14 +08:00
1 parent 7aa5182f3d
commit 079c3eafe9
18 files changed
+1049 -29

No files matched your search

@@ -5,6 +5,7 @@ import type { SourcesRuntime } from "./runtime.ts";
import { refreshSiteAccount } from "./service/account.ts";
import { AUTO_ENRICH_TASK, runAutoEnrich } from "./service/enrich.ts";
import { ingestFeed } from "./service/feeds.ts";
import { emitMetadataRefreshed } from "./service/lifecycle-events.ts";
import { refreshMetadata } from "./service/metadata.ts";
import { resolveRelease } from "./service/resolve.ts";
import { snapshotSite } from "./service/snapshot.ts";
@@ -75,7 +76,18 @@ export function registerSourcesTasks(registry: TaskRegistry, rt: () => SourcesRu
inputSchema: z.object({ mediaEntityId: id, full: z.boolean().optional() }),
retryPolicy: NETWORK_RETRY,
handler: (t) =>
t.step.run("refresh", () => refreshMetadata(rt(), t.input.mediaEntityId, { full: !!t.input.full })),
t.step.run("refresh", async () => {
const r = await refreshMetadata(rt(), t.input.mediaEntityId, { full: !!t.input.full });
// 生命周期广播(ADR-006):元数据已刷新(在调用方发,刷新本身在 metadata.ts)
if (r.status === "refreshed")
await emitMetadataRefreshed(rt().ctx, rt().ctx.db, {
mediaEntityId: t.input.mediaEntityId,
full: !!t.input.full,
changedFields: r.changedFields,
runId: t.runId,
});
return r;
}),
}),
defineTask({
name: "searchSubscription",