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

+5 -2
View File
@@ -38,7 +38,8 @@ const pingModule: ServerModule = {
}),
);
},
outboxRoutes: () => ({ "release.resolved": () => ({ task: "evaluateAcquisition", input: {} }) }),
// 测试专用事件类型:真实事件(如 release.resolved)已由业务模块认领
outboxRoutes: () => ({ "test.ping": () => ({ task: "evaluateAcquisition", input: {} }) }),
};
describe("ServerModule 骨架", () => {
@@ -81,7 +82,9 @@ describe("ServerModule 骨架", () => {
});
it("outbox 路由合并;同一事件被两个模块认领报错", () => {
expect(Object.keys(collectOutboxRoutes([...SERVER_MODULES, pingModule], fakeCtx))).toEqual(["release.resolved"]);
const merged = Object.keys(collectOutboxRoutes([...SERVER_MODULES, pingModule], fakeCtx));
expect(merged).toContain("test.ping");
expect(merged).toContain("release.resolved");
expect(() => collectOutboxRoutes([pingModule, { ...pingModule, name: "dup" }], fakeCtx)).toThrow(/重复认领/);
});