feat(download): 下载器可设「H&R 未知也可清理」(M2)

下载器策略加 hnrUnknownCleanable(缺省关,保持现状):保种天数到期、站点 H&R 要求未知
(M-Team、接管任务、站点没配 H&R)的任务视为可清理;H&R 明确未满足的不受影响。
空间不足事项里写明有多少任务因 H&R 未知没清理、可在哪里放行。

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-03 23:20:29 +08:00
1 parent 3d3a4cefc6
commit 07d5f42143
3 files changed
+30 -8

No files matched your search

@@ -1272,8 +1272,9 @@ export async function cleanupCandidates(d: AoDeps, v: VolumeState): Promise<Clea
const site = rel
? await db.selectFrom("site").select("config").where("id", "=", rel.site_id).executeTakeFirst()
: null;
const days = instancePolicy(topo.inst).seedRetentionDays ?? retention.seedRetentionDays;
const lock = await lockReason(d, row, rel ?? null, site ?? null, days, now);
const policy = instancePolicy(topo.inst);
const days = policy.seedRetentionDays ?? retention.seedRetentionDays;
const lock = await lockReason(d, row, rel ?? null, site ?? null, days, now, policy.hnrUnknownCleanable === true);
out.push({ row, topo, bytes: row.size_bytes ?? 0, cleanable: lock === null, lockReason: lock });
}
}
@@ -1287,10 +1288,13 @@ async function lockReason(
site: { config: unknown } | null,
days: number,
now: Date,
/** 下载器设置「H&R 未知也可清理」:保种天数到期后,H&R 要求未知按已满足处理 */
hnrUnknownCleanable = false,
): Promise<string | null> {
if (!retentionElapsed(row, days, now)) return "retention_days";
const hnr = hnrSatisfied(row, rel as never, site as never);
if (hnr !== true) return hnr === false ? "hnr_unsatisfied" : "hnr_unknown";
if (hnr === false) return "hnr_unsatisfied";
if (hnr === "unknown" && !hnrUnknownCleanable) return "hnr_unknown";
// 已验证入库:最近一次整理记录成功,且没有失败/排队的文件(部分入库不能整种清理)
const rec = await d.ctx.db
.selectFrom("organization_record")
@@ -1385,11 +1389,16 @@ export async function cleanupDownloadCache(
if (pct > th.cleanupTargetPercent && pct >= th.hardThresholdPercent) {
entry.action = entry.removed.length > 0 ? "cleaned" : "insufficient";
// 没有可清理对象:待处理 + 阈值继续阻止新下载(不突破保留要求)
const hnrUnknown = entry.skipped.filter((x) => x.reason === "hnr_unknown").length;
await upsertDecision(d, db, {
kind: "space_insufficient",
severity: "critical",
title: `下载缓存卷 ${v.volumeKey} 空间不足`,
summary: `使用率 ${pct.toFixed(1)}%,可清理任务已用尽;新下载已暂停`,
summary:
`使用率 ${pct.toFixed(1)}%,可清理任务已用尽;新下载已暂停` +
(hnrUnknown
? `。另有 ${hnrUnknown} 个任务因为 H&R 要求未知没有清理,确认没有 H&R 要求的话,可在下载器设置里打开「H&R 未知也可清理」`
: ""),
subjectType: "cache_volume",
dedupeKey: `space_insufficient:${v.volumeKey}`,
evidence: { usedPercent: pct, locked: entry.skipped },