Dependency-aware maintenance window collision auditing, change freezes, policy checks and safe-slot planning for MoonBit
git clone https://github.com/chen-657/moon-change-window-auditor
cd moon-change-window-auditor
moon check --deny-warn --target all// moon.pkg
import {
"chen-657/moon-change-window-auditor" @audit,
}id=database;service=orders-db;start=2026-10-01T22:00;end=2026-10-01T23:00;risk=high;depends=
id=api;service=orders-api;start=2026-10-01T22:45;end=2026-10-01T23:15;risk=medium;depends=database
id=blackout;service=company;kind=freeze;start=2026-12-24T00:00;end=2026-12-27T00:00| 字段 | 必填 | 说明 |
|---|---|---|
| id | 是 | 记录标识。ASCII 字母、数字、_、.、-,不超过 64 字符,全文件唯一 |
| service | 否 | 服务名,只用于报告与汇总,可以为空 |
| start | 是 | YYYY-MM-DDTHH:MM,也接受用空格代替 T |
| end | 是 | 同上,必须晚于 start |
| risk | 否 | low、medium、high 之一,缺省 medium,大小写敏感;冻结期忽略此字段 |
| depends | 否 | 逗号分隔的记录 ID,空值表示没有依赖;冻结期不允许声明依赖 |
| kind | 否 | change(缺省,也接受 window)或 freeze,后者表示变更冻结期 |
// 1. 解析
let plan = @audit.parse_plan(source)
// 2. 审计:重叠、依赖顺序、冻结期冲突
let report = @audit.audit(plan)
println(@audit.render_report(report))
// 3. 按策略判定能否放行
let policy_report = @audit.audit_with_policy(plan, @audit.Policy::strict())
println(@audit.render_policy_report(policy_report))
// 4. 找可安排新变更的空档,并检查候选窗口
match (@audit.parse_instant("2026-10-01T20:00"), @audit.parse_instant("2026-10-02T02:00")) {
(Some(from), Some(to)) => {
println(@audit.render_slots(@audit.safe_slots(plan, from, to, 30)))
let candidate = @audit.candidate_slot(from, from + 60)
println(@audit.can_schedule(plan, candidate, [], 30).to_line())
}
_ => println("invalid horizon")
}| 领域 | 主要接口 |
|---|---|
| 时间 | parse_instant、format_instant、format_duration |
| 解析与模型 | parse_plan、Plan::find、Plan::changes、Plan::freezes、Plan::windows_of_kind、Window::duration、Window::is_freeze |
| 审计 | audit、freeze_conflicts、freezes_hit_by、AuditReport::status、AuditReport::error_count |
| 区间代数 | merge_slots、sort_slots、clip_slot、invert_slots、intersect_slots、subtract_slots、total_duration、slot_at、Slot::contains、Slot::overlaps、Slot::touches、Slot::intersect、Slot::merge_with |
| 阻塞与空档 | blocked_intervals、blocked_minutes、safe_slots |
| 并发负载 | concurrency_at、service_concurrency_at、peak_concurrency、busiest_intervals、max_concurrency_in、load_profile、change_minutes、freeze_minutes |
| 依赖图 | dependencies_of、dependents_of、transitive_dependencies、dependency_depth、find_cycles、graph_roots、graph_leaves、execution_order |
| 排期 | can_schedule、earliest_start、slack_minutes、earliest_schedule、next_slots、candidate_slot |
| 策略 | Policy::standard、Policy::strict、Policy::relaxed、Policy::describe、audit_with_policy、PolicyReport::status |
| 汇总与对比 | summarize、find_service、diff_plans、PlanDiff::is_empty |
| 渲染 | render_report、render_slots、render_policy_report、render_summary、render_diff、render_execution_order、render_schedule_result、render_timeline、render_report_json、render_plan_json、render_slots_json、render_report_markdown、render_windows_markdown、render_slots_markdown、render_windows_csv、render_overlaps_csv、render_freeze_conflicts_csv、render_dependency_issues_csv、render_slots_csv |
| code | 严重度 | 触发条件 | subject |
|---|---|---|---|
| max-concurrent | error | 同时运行的变更数超过 max_concurrent(先全计划,再逐服务) | all 或服务名 |
| max-change-minutes | error | 单条变更时长超过 max_change_minutes(不计冻结期) | 记录 ID |
| min-dependency-slack | warning | 依赖结束到依赖方开始的余量小于 min_dependency_slack_minutes | 记录 ID |
| high-risk-overlap | error | forbid_high_risk_overlap 为真且高风险变更与其他变更重叠(每对一次) | 高风险记录 ID |
| max-high-risk-per-day | error | 某一天的高风险变更数超过 max_high_risk_per_day | 当天 00:00 |
| freeze-conflict | error | forbid_freeze_conflicts 为真且变更落在冻结期内 | 记录 ID |
| service-missing | warning | require_service 为真且变更没有服务名 | 记录 ID |
Timeline 2026-10-01T21:00 -> 2026-10-02T04:00 (7h, 28 columns, 1 column = 15m)
database |....####....................|
api |......########..............|
blackout |................====!!!!....|
cache |....................!!!!##..|
legend: . free, # change, = freeze, ! change inside freezemoon run examples/demo # 审计一个含重叠、依赖错误、缺失依赖的计划
moon run examples/policy-gate # 按严格策略判定计划,并输出汇总、排期与时间线POLICY VIOLATIONS
warning min-dependency-slack (api): overlaps database and misses the minimum slack of 30m by 30m
error high-risk-overlap (database): high risk database overlaps api for 30m
error freeze-conflict (cache): runs inside freeze blackout for 1h
policy: FAIL
Plan summary
changes: 3
freezes: 1
blocked: 5h
workload: 4h30m
peak: 2 changes at 2026-10-01T22:30
Earliest dependency aware schedule (3 changes)
database 2026-10-01T22:00 -> 2026-10-01T23:00 (+0m)
api 2026-10-01T23:00 -> 2026-10-02T01:00 (+30m)
cache 2026-10-02T02:00 -> 2026-10-02T03:30 (+0m)moon version --all
moon check --deny-warn --target all
moon build --target all
moon test --target all
moon fmt
moon info
moon run examples/demo
moon run examples/policy-gate| 文件 | 职责 |
|---|---|
| moon.mod / moon.pkg | 模块元数据;库包不导入任何依赖 |
| text.mbt | 数字解析、补齐、拼接、标识符校验、字符串比较与排序 |
| time.mbt | 分钟轴、YYYY-MM-DDTHH:MM 解析与格式化、时长渲染 |
| types.mbt | 数据模型:Plan、Window、Slot、Overlap、FreezeConflict、DependencyIssue、AuditReport 等 |
| parse.mbt | 文本格式解析与全部解析诊断 |
| intervals.mbt | 半开区间代数、合并、取反、交、差 |
| audit.mbt | 重叠检测、依赖顺序检查、冻结期冲突 |
| concurrency.mbt | 并发计数、峰值、拥挤时段、负载采样 |
| graph.mbt | 依赖图查询、环检测、拓扑执行顺序 |
| schedule.mbt | 候选窗口检查、最早开始、依赖感知排期 |
| policy.mbt | 策略模型、违规推导、策略报告 |
| summary.mbt | 计划汇总与按服务统计 |
| diff.mbt | 两版计划对比 |
| report.mbt | 文本报告 |
| render_json.mbt / render_markdown.mbt / render_csv.mbt / render_timeline.mbt | 其余四种输出 |
| examples/demo / examples/policy-gate | 可运行示例 |
| *_test.mbt | 测试包 |
| .github/workflows/check.yml | CI:check、build、test、示例、格式与接口一致性 |
pub(all) struct AuditReport {
plan : Plan
overlaps : Array[Overlap]
dependency_issues : Array[DependencyIssue]
freeze_conflicts : Array[FreezeConflict]
blocked_minutes : Int
}pub(all) struct DependencyIssue {
window_id : String
dependency : String
problem : DependencyProblem
severity : Severity
detail : String
}pub(all) enum DependencyProblem {
MissingDependency
SelfDependency
OutOfOrder
BackToBack
} derive(Eq)pub(all) struct FreezeConflict {
window_id : String
freeze_id : String
minutes : Int
}pub(all) struct LoadPoint {
start : Int
count : Int
}pub(all) struct PeakConcurrency {
minute : Int
count : Int
}pub(all) struct PlanDiff {
changes : Array[WindowChange]
added_windows : Int
removed_windows : Int
modified_windows : Int
new_overlaps : Array[Overlap]
resolved_overlaps : Int
new_freeze_conflicts : Array[FreezeConflict]
resolved_freeze_conflicts : Int
}pub(all) struct PlanSummary {
changes : Int
freezes : Int
blocked_minutes : Int
change_minutes : Int
low_risk : Int
medium_risk : Int
high_risk : Int
earliest : Int?
latest : Int?
span_minutes : Int
peak : PeakConcurrency?
services : Array[ServiceSummary]
}pub(all) struct Policy {
max_concurrent : Int
max_change_minutes : Int
min_dependency_slack_minutes : Int
forbid_high_risk_overlap : Bool
max_high_risk_per_day : Int
forbid_freeze_conflicts : Bool
require_service : Bool
}pub(all) struct PolicyReport {
plan : Plan
audit : AuditReport
policy : Policy
violations : Array[PolicyViolation]
}pub(all) struct PolicyViolation {
code : String
severity : Severity
subject : String
detail : String
}pub(all) struct ScheduleCheck {
candidate : Slot
allowed : Bool
reasons : Array[String]
change_conflicts : Array[SlotConflict]
freeze_ids : Array[String]
}pub(all) struct ScheduledChange {
id : String
start : Int
end : Int
original_start : Int
shift_minutes : Int
}pub(all) struct ServiceSummary {
service : String
changes : Int
blocked_minutes : Int
peak_concurrency : Int
high_risk_changes : Int
}pub(all) struct Slot {
start : Int
end : Int
}pub(all) struct SlotConflict {
window_id : String
minutes : Int
}pub(all) struct Window {
id : String
service : String
start : Int
end : Int
risk : Risk
depends : Array[String]
kind : WindowKind
}fn can_schedule(plan : Plan, candidate : Slot, depends : Array[String], min_minutes : Int) -> ScheduleCheckfn csv_escape(text : String) -> Stringfn format_duration(minutes : Int) -> Stringfn format_instant(minutes : Int) -> Stringfn json_escape(text : String) -> Stringfn parse_instant(text : String) -> Int?Install
Download zipDependency-aware maintenance window collision auditing, change freezes, policy checks and safe-slot planning for MoonBit