import { z } from "zod"; import { EntityBase, Id, JsonObject, NonNegInt, SERVER_MANAGED, Timestamp } from "./common"; import { AcquisitionPriority } from "./policy"; // 执行域:业务任务、下载计划、下载任务、操作账本(REQ §8.4、§11;ADR-002) /** 持久任务类型(任务书 §3.1-8)+ Agent 有界任务 */ export const TaskKind = z.enum([ "ingestFeed", "resolveObservation", "evaluateAcquisition", "submitDownload", "verifyAndImport", "refreshMetadata", "reconcileLibrary", "cleanupDownloadCache", "resolveDecision", "agentRun", ]); export type TaskKind = z.infer; export const TaskStatus = z.enum(["pending", "running", "waiting", "succeeded", "failed", "cancelled", "dead"]); export type TaskStatus = z.infer; export const Task = EntityBase.extend({ kind: TaskKind, handlerVersion: z.number().int().min(1), idempotencyKey: z.string().min(1), status: TaskStatus, /** 数值越小越先执行 */ priority: z.number().int(), input: JsonObject, output: JsonObject.nullable(), subjectType: z.string().nullable(), subjectId: Id.nullable(), parentTaskId: Id.nullable(), engineRunId: z.string().nullable(), attempts: NonNegInt, maxAttempts: z.number().int().min(1), nextRunAt: Timestamp, waitReason: z.string().nullable(), cancelRequestedAt: Timestamp.nullable(), cancelReason: z.string().nullable(), lastError: JsonObject.nullable(), startedAt: Timestamp.nullable(), finishedAt: Timestamp.nullable(), triggeredBy: z.string().nullable(), }); export type Task = z.infer; /** 内部创建输入(幂等键由服务端按语义生成,不能由模型挑选) */ export const TaskCreate = Task.pick({ kind: true, handlerVersion: true, idempotencyKey: true, priority: true, input: true, subjectType: true, subjectId: true, parentTaskId: true, maxAttempts: true, nextRunAt: true, triggeredBy: true, }).partial({ handlerVersion: true, priority: true, input: true, subjectType: true, subjectId: true, parentTaskId: true, maxAttempts: true, nextRunAt: true, triggeredBy: true, }); export type TaskCreate = z.infer; export const TaskUpdate = Task.pick({ status: true, output: true, engineRunId: true, attempts: true, nextRunAt: true, waitReason: true, lastError: true, startedAt: true, finishedAt: true, }).partial(); export type TaskUpdate = z.infer; /** 取消 = 停止未来步骤,不撤销已完成外部动作(REQ §11.4) */ export const TaskCancelRequest = z.object({ reason: z.string().min(1).max(500) }); export type TaskCancelRequest = z.infer; export const PlanPurpose = z.enum(["fill", "upgrade", "replace_source"]); export type PlanPurpose = z.infer; export const PlanStatus = z.enum(["draft", "reserved", "submitted", "completed", "cancelled", "failed", "expired"]); export type PlanStatus = z.infer; /** 下载计划:与 Slot 预留、预算预留同事务创建;一个 Slot 同时只能有一个 reserved/submitted 计划 */ export const AcquisitionPlan = EntityBase.extend({ slotId: Id, sourceReleaseId: Id, taskId: Id.nullable(), purpose: PlanPurpose, status: PlanStatus, priority: AcquisitionPriority, /** evaluateAcquisition 的确定性评估结果与拒绝/接受理由 */ evaluation: JsonObject, reservedBytes: NonNegInt, budgetReservation: JsonObject, payloadHash: z.string().min(1), expectedRevision: z.number().int().min(1), idempotencyKey: z.string().min(1), expiresAt: Timestamp.nullable(), statusReason: z.string().nullable(), }); export type AcquisitionPlan = z.infer; export const AcquisitionPlanCreate = AcquisitionPlan.omit({ ...SERVER_MANAGED, status: true }).partial({ taskId: true, purpose: true, priority: true, evaluation: true, reservedBytes: true, budgetReservation: true, expectedRevision: true, expiresAt: true, statusReason: true, }); export type AcquisitionPlanCreate = z.infer; export const AcquisitionPlanUpdate = AcquisitionPlan.pick({ status: true, taskId: true, statusReason: true }).partial(); export type AcquisitionPlanUpdate = z.infer; export const DownloadOrigin = z.enum(["submitted", "adopted"]); export type DownloadOrigin = z.infer; export const DownloadStatus = z.enum([ "queued", "submitting", "downloading", "paused", "stalled", "completed", "importing", "imported", "failed", "removed", "missing", ]); export type DownloadStatus = z.infer; /** 暂停原因:阈值恢复时只恢复 threshold 暂停项,不恢复用户手动暂停(REQ §8.4.1) */ export const PauseReason = z.enum(["user", "threshold", "policy", "agent"]); export type PauseReason = z.infer; export const TorrentHash = z.string().regex(/^[0-9a-f]{40}$|^[0-9a-f]{64}$/); export const DownloadTask = EntityBase.extend({ instanceId: Id, /** 该下载器实例内的 torrent 标识(v1 hash 或 v2 hash,小写十六进制) */ torrentHash: TorrentHash, infohashV1: z.string().nullable(), infohashV2: z.string().nullable(), acquisitionPlanId: Id.nullable(), sourceReleaseId: Id.nullable(), name: z.string(), savePath: z.string().nullable(), contentPath: z.string().nullable(), category: z.string().nullable(), tags: z.array(z.string()), /** 是否在管理标签范围内;标签移除后暂停后续自动动作 */ managed: z.boolean(), origin: DownloadOrigin, status: DownloadStatus, pauseReason: PauseReason.nullable(), rawState: z.string().nullable(), progress: z.number().min(0).max(1), sizeBytes: NonNegInt.nullable(), downloadedBytes: NonNegInt, remainingBytes: NonNegInt.nullable(), ratio: z.number().nullable(), seedingTimeSec: NonNegInt.nullable(), completedAt: Timestamp.nullable(), lastProgressAt: Timestamp.nullable(), /** 无进展起点:只记录并汇入待处理,不自动换源(REQ §8.4.2) */ noProgressSince: Timestamp.nullable(), lastSyncedAt: Timestamp.nullable(), nextCheckAt: Timestamp.nullable(), removedAt: Timestamp.nullable(), fileList: z.array(z.object({ path: z.string(), size: NonNegInt, progress: z.number().optional() })).nullable(), lastError: JsonObject.nullable(), }); export type DownloadTask = z.infer; export const DownloadTaskCreate = DownloadTask.pick({ instanceId: true, torrentHash: true, infohashV1: true, infohashV2: true, acquisitionPlanId: true, sourceReleaseId: true, name: true, savePath: true, category: true, tags: true, managed: true, origin: true, }).partial({ infohashV1: true, infohashV2: true, acquisitionPlanId: true, sourceReleaseId: true, savePath: true, category: true, tags: true, managed: true, origin: true, }); export type DownloadTaskCreate = z.infer; export const DownloadTaskUpdate = DownloadTask.omit({ ...SERVER_MANAGED, instanceId: true, torrentHash: true, }).partial(); export type DownloadTaskUpdate = z.infer; export const OperationPhase = z.enum(["decision", "prepare", "transfer", "verify", "commit", "cleanup", "compensate"]); export type OperationPhase = z.infer; export const OperationKind = z.enum([ "copy", "move", "rename", "mkdir", "hardlink", "delete_source", "delete_target", "verify", "register", "qb_pause", "qb_resume", "qb_remove", "qb_set_tags", "other", ]); export type OperationKind = z.infer; export const OperationStatus = z.enum(["planned", "started", "succeeded", "failed", "skipped", "compensated"]); export type OperationStatus = z.infer; /** 副作用账本:plan → attempt(started, attemptToken) → result;提交时以 attemptToken 做 fencing */ export const AssetOperation = EntityBase.extend({ operationId: z.string().min(1), taskId: Id.nullable(), organizationRecordId: Id.nullable(), planFingerprint: z.string().nullable(), ordinal: z.number().int().nonnegative(), phase: OperationPhase, kind: OperationKind, status: OperationStatus, intent: JsonObject, attemptToken: Id.nullable(), attempts: NonNegInt, result: JsonObject.nullable(), error: JsonObject.nullable(), mediaAssetId: Id.nullable(), downloadTaskId: Id.nullable(), startedAt: Timestamp.nullable(), finishedAt: Timestamp.nullable(), }); export type AssetOperation = z.infer; export const AssetOperationCreate = AssetOperation.pick({ operationId: true, taskId: true, organizationRecordId: true, planFingerprint: true, ordinal: true, phase: true, kind: true, intent: true, mediaAssetId: true, downloadTaskId: true, }).partial({ taskId: true, organizationRecordId: true, planFingerprint: true, ordinal: true, intent: true, mediaAssetId: true, downloadTaskId: true, }); export type AssetOperationCreate = z.infer; export const AssetOperationUpdate = AssetOperation.pick({ status: true, attemptToken: true, attempts: true, result: true, error: true, mediaAssetId: true, startedAt: true, finishedAt: true, }).partial(); export type AssetOperationUpdate = z.infer;