feat(db,contracts): W5-C subscription units migration 0025, contracts and mapping

- subscription: status pending (TMDB not ready: searchable/evaluable, not completable),
  last_searched_at / next_search_at
- content_unit (season): episode_total ledger with source / lock / timestamp
- acquisition_plan: unit_keys (Slot × unit reservation) and file_selection; drop the
  one-in-flight-per-slot unique index (checked under slot row lock per unit instead)
- new tables slot_deferral, slot_unit (per-unit quality ledger), subscription_history
- contracts: SubscriptionParams, SubscriptionHistory, SlotDeferral, SlotUnit, SubscriptionProgress,
  EpisodeTotalSet, CandidateEvaluation pri/trace/unitKeys and decision deferred;
  endpoints subscriptions.search/progress/setEpisodeTotal and subscription-history
- mapping: evaluation messages via describeViolation, pri/trace passthrough

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 23:33:56 +08:00
1 parent d162d464c4
commit 1dd460d359
9 files changed
+405 -34

No files matched your search

+27 -16
View File
@@ -4,14 +4,15 @@ import type {
SlotStatus as ContractSlotStatus,
JsonObject,
} from "@umt/contracts";
import type {
SlotStatus as DomainSlotStatus,
SlotCompletionSpec,
SlotEvaluation,
SlotHardConditions,
SlotSoftPreferences,
SlotSpec,
SlotUpgradePolicy,
import {
type SlotStatus as DomainSlotStatus,
describeViolation,
type SlotCompletionSpec,
type SlotEvaluation,
type SlotHardConditions,
type SlotSoftPreferences,
type SlotSpec,
type SlotUpgradePolicy,
} from "@umt/domain";
// Slot:状态两侧取值相同(REQ §6.1),映射表只为让任一侧增删值时编译/测试失败。
@@ -92,26 +93,34 @@ export interface ContractSlotEvaluation {
/** 与 DownloadPlanOutput.rejected 同形:硬条件不满足 / 字段未知的原因 */
rejected: { code: string; message: string }[];
reasons: string[];
/** 优先级阶梯 pri(未配置阶梯为 100;拒绝/未知为 null) */
pri: number | null;
/** 可读说明(逐条违规 + 逐层阶梯) */
trace: string[];
}
/** domain 评估结果 → 能力输出/决策记录用的扁平表示 */
export function toContractSlotEvaluation(ev: SlotEvaluation): ContractSlotEvaluation {
const trace = [...(ev.trace ?? [])];
switch (ev.decision) {
case "accept":
case "upgrade":
return { decision: ev.decision, acceptable: true, rejected: [], reasons: [...ev.reasons] };
return {
decision: ev.decision,
acceptable: true,
rejected: [],
reasons: [...ev.reasons],
pri: ev.pri ?? null,
trace,
};
case "reject":
return {
decision: "reject",
acceptable: false,
rejected: ev.violations.map((v) => ({
code: `${v.field}.${v.code}`,
message:
v.expected === undefined
? `${v.field}: ${v.code}`
: `${v.field}: ${v.code}(期望 ${JSON.stringify(v.expected)},实际 ${JSON.stringify(v.actual ?? null)})`,
})),
rejected: ev.violations.map((v) => ({ code: `${v.field}.${v.code}`, message: describeViolation(v) })),
reasons: [],
pri: ev.ladder?.pri ?? null,
trace,
};
case "unknown_field":
return {
@@ -119,6 +128,8 @@ export function toContractSlotEvaluation(ev: SlotEvaluation): ContractSlotEvalua
acceptable: false,
rejected: ev.missing.map((m) => ({ code: `unknown_field.${m}`, message: `字段未知,需要补充: ${m}` })),
reasons: [],
pri: null,
trace,
};
}
}