feat(server): W2-C rules, downloads, import, organization records, library and dashboard

- Rules API: subscriptions, acquisition slots (revision lock), policy revisions
  (draft -> publish = automation_grant, rollback as new revision), preference
  tags with suppression records (agent cannot re-add suppressed fingerprints).
- Tasks: evaluateAcquisition (mode/evaluate/budget/cache volume -> frozen plan
  with slot reservation, candidate reasons as evidence artifacts),
  submitDownload (withAttemptToken fencing, already_exists adoption),
  verifyAndImport (frozen organization_record + file results, resumable
  copy/move/cross-volume steps, assets/locations/bindings in one tx),
  cleanupDownloadCache (verified+retention+H&R only, assertSafeToDeleteData),
  reconcileLibrary (root sentinel, suspected -> confirmed missing).
- Worker loop: qB sync with tag adoption, no-progress evidence, hard threshold
  pause / resume-watermark resume, sweepers for deferred plans and imports.
- Public organization records API incl. changes cursor (change_xid guard),
  media library list/detail, inventory overlay, Emby inventory, dashboard.
- Contracts: SlotTemplate, AcquisitionPolicyRules, CandidateEvaluation,
  library/inventory/dashboard read models.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 20:08:47 +08:00
1 parent 1cd523d3e6
commit 829ca4f7ac
24 files changed
+7488 -9

No files matched your search

+51
View File
@@ -245,3 +245,54 @@ export type PreferenceSuppressionCreate = z.infer<typeof PreferenceSuppressionCr
/** 唯一允许的更新是解除抑制 */
export const PreferenceSuppressionUpdate = z.object({ active: z.literal(false), reason: z.string().optional() });
export type PreferenceSuppressionUpdate = z.infer<typeof PreferenceSuppressionUpdate>;
// ── 收录策略规则与候选评估(W2-C) ──
/** Slot 模板:策略规则里用于自动建 Slot(新作品收录 / 订阅缺省 Slot);字段同 AcquisitionSlot 规则列 */
export const SlotTemplate = z.object({
name: z.string().min(1).max(64),
hardConditions: JsonObject.optional(),
softPreferences: JsonObject.optional(),
fallback: JsonObject.optional(),
upgradeThreshold: JsonObject.optional(),
completion: JsonObject.optional(),
});
export type SlotTemplate = z.infer<typeof SlotTemplate>;
/**
* kind=acquisition 的 policy_revision.rules 结构(结构化数据,不是可执行脚本)。
* 发布即对 scope 范围内的正常下载/追更/整理生成持续授权 automation_grant(REQ §6.4)。
*/
export const AcquisitionPolicyRules = z.object({
/** 新作品收录:发行/首播在窗口内且库里没有的作品,按模板建订阅与 Slot */
newTitle: z
.object({
enabled: z.boolean(),
windowDays: z.number().int().min(1).max(3650).optional(),
mediaKinds: z.array(SubscriptionMediaKind).optional(),
slots: z.array(SlotTemplate),
})
.optional(),
/** 订阅没有 Slot 时套用的缺省 Slot */
defaultSlots: z.array(SlotTemplate).optional(),
/** 整理方式覆盖(缺省读 settings.organize.defaultMode) */
organizeMode: z.enum(["copy", "move"]).optional(),
/** Move 在 H&R 未满足时经授权改用 Copy */
allowCopyWhenHnrPending: z.boolean().optional(),
});
export type AcquisitionPolicyRules = z.infer<typeof AcquisitionPolicyRules>;
/** 候选对某个 Slot 的确定性评估(供作品详情"为什么选/不选") */
export const CandidateEvaluation = z.object({
slotId: Id,
sourceReleaseId: Id,
title: z.string(),
/** accept / upgrade / reject / unknown_field / skipped(被模式、预算、阈值、总开关等挡住) */
decision: z.enum(["accept", "upgrade", "reject", "unknown_field", "skipped"]),
mode: z.enum(["new_title", "catch_up", "upgrade"]).nullable(),
rejected: z.array(z.object({ code: z.string(), message: z.string() })),
reasons: z.array(z.string()),
planId: Id.nullable(),
evaluatedAt: Timestamp,
});
export type CandidateEvaluation = z.infer<typeof CandidateEvaluation>;