New @umt/mapping (depends on domain + contracts only): download status + pause reason, evidence level / media kind, identity resolution status, slot status / spec columns / evaluation, ParsedRelease <-> source_release.parsed, SiteRelease <-> source_release, PathBinding, OrganizePlan -> organization_record / file results / organize.plan output. Tables are Record<union, ...> so adding a value on either side fails typecheck or tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>
88 lines
3.4 KiB
TypeScript
88 lines
3.4 KiB
TypeScript
import type { DownloadStatus, PauseReason } from "@umt/contracts";
|
|
import type { DownloadTaskStatus as DomainDownloadStatus } from "@umt/domain";
|
|
|
|
// 下载状态:domain 只描述"下载器里看到的状态"(mapQbState 的结果),
|
|
// contracts / download_task.status 还叠加业务阶段(submitting / stalled / importing / imported)和 pauseReason。
|
|
|
|
export type { DomainDownloadStatus };
|
|
|
|
export interface ContractDownloadState {
|
|
status: DownloadStatus;
|
|
pauseReason: PauseReason | null;
|
|
}
|
|
|
|
const TO_CONTRACT: Record<DomainDownloadStatus, ContractDownloadState> = {
|
|
queued: { status: "queued", pauseReason: null },
|
|
downloading: { status: "downloading", pauseReason: null },
|
|
paused_by_threshold: { status: "paused", pauseReason: "threshold" },
|
|
paused_by_user: { status: "paused", pauseReason: "user" },
|
|
completed: { status: "completed", pauseReason: null },
|
|
error: { status: "failed", pauseReason: null },
|
|
missing_files: { status: "missing", pauseReason: null },
|
|
removed: { status: "removed", pauseReason: null },
|
|
};
|
|
|
|
/** contracts 状态 → 下载器层面的 domain 状态(业务叠加态折回其底层下载状态) */
|
|
const TO_DOMAIN: Record<DownloadStatus, DomainDownloadStatus | "by_pause_reason"> = {
|
|
queued: "queued",
|
|
// 提交中:下载器里还没有/刚出现,按排队处理
|
|
submitting: "queued",
|
|
downloading: "downloading",
|
|
paused: "by_pause_reason",
|
|
// 无进展是业务判定(detectNoProgress),下载器里仍是 downloading
|
|
stalled: "downloading",
|
|
completed: "completed",
|
|
// 入库阶段:下载本身已完成
|
|
importing: "completed",
|
|
imported: "completed",
|
|
failed: "error",
|
|
removed: "removed",
|
|
missing: "missing_files",
|
|
};
|
|
|
|
/** 暂停原因 → domain 状态:只有 threshold 会被阈值恢复自动恢复,其余都按用户暂停对待(REQ §8.4.1) */
|
|
const PAUSE_TO_DOMAIN: Record<PauseReason, DomainDownloadStatus> = {
|
|
threshold: "paused_by_threshold",
|
|
user: "paused_by_user",
|
|
policy: "paused_by_user",
|
|
agent: "paused_by_user",
|
|
};
|
|
|
|
export const DOMAIN_DOWNLOAD_STATUSES = Object.keys(TO_CONTRACT) as DomainDownloadStatus[];
|
|
|
|
export function toContractDownloadState(s: DomainDownloadStatus): ContractDownloadState {
|
|
return { ...TO_CONTRACT[s] };
|
|
}
|
|
|
|
export function toDomainDownloadStatus(status: DownloadStatus, pauseReason: PauseReason | null): DomainDownloadStatus {
|
|
const d = TO_DOMAIN[status];
|
|
if (d !== "by_pause_reason") return d;
|
|
return PAUSE_TO_DOMAIN[pauseReason ?? "user"];
|
|
}
|
|
|
|
/**
|
|
* 同步轮询时合并"下载器观察"与"当前业务状态":
|
|
* - 业务叠加态在其底层下载状态不变时保留(importing/imported 遇 completed、stalled 遇 downloading、submitting 遇 queued);
|
|
* - 暂停原因为 policy/agent 且下载器仍是暂停时保留原因,不被改写成 user;
|
|
* - 其余以观察为准。
|
|
*/
|
|
export function mergeObservedDownloadState(
|
|
current: ContractDownloadState,
|
|
observed: DomainDownloadStatus,
|
|
): ContractDownloadState {
|
|
const next = TO_CONTRACT[observed];
|
|
const keep =
|
|
((current.status === "importing" || current.status === "imported") && observed === "completed") ||
|
|
(current.status === "stalled" && observed === "downloading") ||
|
|
(current.status === "submitting" && observed === "queued");
|
|
if (keep) return { ...current };
|
|
if (
|
|
current.status === "paused" &&
|
|
observed === "paused_by_user" &&
|
|
(current.pauseReason === "policy" || current.pauseReason === "agent")
|
|
) {
|
|
return { ...current };
|
|
}
|
|
return { ...next };
|
|
}
|