# 资源受限恢复窗口计算

MoonBit 核心接收恢复任务、依赖边、资源名单和目标窗口。每个任务有影响等级、正数持续时间和一个资源；名单中的每个资源表示一个不可同时处理两项任务的团队。任务不能中断，时间从演练起点第 0 分钟计算。

## 计算过程

1. 校验资产、缺失依赖、重复边、环、资源可用性和持续时间；时间统一换算成分钟并检查整数范围。
2. 从依赖图反向计算每个节点的最高下游影响等级和最长剩余路径。每轮只考虑前置任务已排程的节点，依次按下游影响等级、下游剩余路径、当前影响等级、较短任务和资产 ID 选择。这样低等级的关键前置组件也能先于无关业务排程。
3. 对选中的任务，从所有直接依赖的最晚结束时间起，查找所需资源上最早可容纳完整任务的空档；该空档起点为 `start`，`end = start + duration`。不同资源上的独立任务可以重叠；较晚选中的任务也能填入较早的空档。
4. 恢复窗口是 `max(所有任务的 end)`，不是任务时长总和。窗口余量为 `目标窗口 - 恢复窗口`。
5. 同时计算两个不可突破的下界：最长依赖路径的时长、单个资源上的最大总负载。其最大值超过目标窗口时，目标被证明无法满足；否则仍须检查实际排程。

这是确定性的最早可行空档列表排程，不保证找到全局最短工期。如果下界未超标但排程超标，错误明确报告“本次排程超标”和下界，不能据此断言所有可行排程都超标。RTO 等 BIA 目标由调用者评估后传入 `target_window`；当前算法不自动把 RTO 当作每项任务的截止时间。

## 可运行案例

在仓库根目录运行 `moon run examples/recovery_window`。输入与输出如下：

| 任务 | 资源 | 持续时间 | 前置任务 | 排程 |
| --- | --- | ---: | --- | --- |
| postgres-primary | database | 10 分钟 | 无 | 0–10 |
| checkout-api | compute | 20 分钟 | postgres-primary | 10–30 |
| checkout-worker | compute | 15 分钟 | 无 | 30–45 |
| edge-router | network | 25 分钟 | 无 | 0–25 |

串行相加需要 70 分钟；允许独立资源并行后，本例为 45 分钟，恰好满足 45 分钟目标。最长依赖路径下界是 30 分钟，最忙资源的负载下界是 35 分钟。若只给 checkout-api 与 checkout-worker 一个 30 分钟的 compute 窗口，它们的资源负载至少 35 分钟，算法会拒绝该目标。

`RecoveryPlan.schedule` 按任务被选中的顺序返回资源、开始和结束时间；并行任务可能排在数组末尾但较早启动，应以时间字段为准。`total_time_minutes` 返回实际排程窗口，并附带两个下界与余量。静态 Web 工作区目前展示的是演示数据的串行估算；需要资源受限排程时，请调用 MoonBit API 或运行此示例。

交叉依赖回归用例还验证了空档填充：高优先级任务先占用第二个团队的第 2–4 分钟，随后选择的低优先级前置任务仍能占用该团队的第 0–2 分钟。两条依赖链因此都在第 4 分钟完成，不会因列表选择顺序误报超窗。
