canaryring

    Deterministic canary rollout planner and blast-radius simulator for MoonBit

    canary
    rollout
    feature-flag
    sre
    simulator
    Download zip
    Author
    Version
    0.1.0
    License
    MIT
    Last updated
    2 months ago
    Downloads
    34

    No dependencies

    #CanaryRing

    CanaryRing 是一个用 MoonBit 实现的确定性金丝雀发布规划与爆炸半径模拟库。它面向需要灰度发布、阶段放量、指标闸门和回滚证据的工程场景:给定用户画像、分群规则、发布阶段和风险预算,CanaryRing 可以稳定地计算哪些用户会被选中、当前阶段能否继续推进、风险是否需要暂停或回滚,并输出 Markdown / JSON 报告。

    项目边界很明确:CanaryRing 不做 README 检查、不做开源来源证明、不做报名材料审计,也不依赖 GitHub 或 Mooncakes 网络接口。核心价值是一个纯 MoonBit、可测试、可复用的 progressive delivery 规则引擎。

    #功能

    • 稳定哈希桶:同一用户、同一 salt 始终进入同一灰度桶。
    • 分群规则:支持地区、账号等级、标签、版本号、排除规则和预分桶。
    • 阶段计划:支持 Shadow、DryRun、Live、KillSwitch 四类阶段。
    • 风险预算:检查错误率、p95 延迟、样本数、饱和度、依赖失败、合成检查和爆炸半径。
    • 爆炸半径:同时统计用户数、流量权重、地区数、账号等级数和风险分。
    • 仿真报告:输出 Markdown 和 JSON,便于 README、CI 日志和运维记录复用。
    • 控制策略:内置 35 条可执行控制项,用于发布前自查。
    • 计划差异:比较两个发布计划的风险变化。
    • 时间线:生成阶段开始、结束、审批和指标检查点。
    • 事故报告:根据失败指标生成回滚行动和客户说明草稿。
    • 流量预测:按基础流量、增长率、峰值倍数和安全余量预测各阶段压力。

    #安装

    发布到 mooncakes.io 后可使用:

    moon add EJJ-ai-nb/canaryring

    本仓库本地运行不需要额外第三方库。

    #快速使用

    fn main {
    let plan = @canaryring.example_plan()
    let users = @canaryring.example_users()
    let metrics = @canaryring.healthy_metrics()
    let report = @canaryring.simulate_rollout(plan, users, metrics)
    println(report.to_markdown())
    }

    运行示例:

    moon run examples/basic moon run cmd/main

    #常用命令

    moon check --deny-warn moon build moon test --deny-warn moon fmt --check moon doc

    #项目结构

    • model.mbt:核心数据模型。
    • hash.mbt、segment.mbt、plan.mbt:稳定分桶、分群匹配、发布计划验证。
    • gate.mbt、slo.mbt、blast.mbt:指标闸门、窗口趋势、爆炸半径。
    • simulate.mbt、strategy.mbt:发布仿真和下一阶段建议。
    • policy.mbt、diff.mbt、timeline.mbt、incident.mbt、forecast.mbt:新增的发布控制、计划对比、时间线、事故复盘和流量预测能力。
    • examples/basic、cmd/main:可运行示例。
    • docs/:API、设计、测试记录、工单记录和开源合规说明。

    #验收准备状态

    • 主要实现语言:MoonBit。
    • 包名:EJJ-ai-nb/canaryring。
    • 计划仓库:https://github.com/EJJ-ai-nb/canaryring。
    • 许可证:MIT。
    • 当前本地测试:moon check --deny-warn 通过,moon test --deny-warn 通过。
    • 当前有效 MoonBit 代码规模:超过 4,000 行。
    • CI:已配置 GitHub Actions,包含 check、build、fmt、test、示例 smoke test。
    • Mooncakes:本地已按 moon.mod 准备,最终验收前需要在正确账号下发布。

    #开源合规

    CanaryRing 为原创 MoonBit 实现。仓库不包含第三方代码、图片、音频、字体或外部数据集;仅使用 MoonBit 标准工具链和 moonbitlang/core/test 测试包。项目根目录保留 MIT License。

    BlastRadius

    pub(all) struct BlastRadius {
    exposed_users : Int
    exposed_traffic_units : Int
    region_count : Int
    tier_count : Int
    percent_bp : Int
    risk_score : Int
    } derive(Eq,
    Debug
    )

    ControlDomain

    pub(all) enum ControlDomain {
    OwnerEvidence
    SegmentEvidence
    ScheduleEvidence
    MetricEvidence
    BlastEvidence
    RollbackEvidence
    ReleaseDomain
    } derive(Eq,
    Debug
    )

    ControlEvaluation

    pub(all) struct ControlEvaluation {
    rule_id : String
    domain : ControlDomain
    status : Severity
    score : Int
    title : String
    message : String
    repair : String
    } derive(Eq,
    Debug
    )

    ControlReview

    pub(all) struct ControlReview {
    verdict : String
    score : Int
    passed : Int
    warned : Int
    failed : Int
    evaluations : Array[ControlEvaluation]
    } derive(Eq,
    Debug
    )

    ControlReview::to_json

    fn ControlReview::to_json(self : ControlReview) -> String

    ControlReview::to_markdown

    fn ControlReview::to_markdown(self : ControlReview) -> String

    ControlRule

    pub(all) struct ControlRule {
    id : String
    domain : ControlDomain
    title : String
    check_text : String
    evidence_text : String
    repair_text : String
    weight : Int
    } derive(Eq,
    Debug
    )

    ControlRule::ControlRule

    fn ControlRule::ControlRule(id? : String, domain? : ControlDomain, title? : String, check_text? : String, evidence_text? : String, repair_text? : String, weight? : Int) -> ControlRule

    EvidenceReport

    pub(all) struct EvidenceReport {
    score : Int
    verdict : String
    findings : Array[RolloutFinding]
    } derive(Eq,
    Debug
    )

    EvidenceReport::to_markdown

    fn EvidenceReport::to_markdown(self : EvidenceReport) -> String

    ExposureDecision

    pub(all) struct ExposureDecision {
    enabled : Bool
    bucket_bp : Int
    mode : RolloutMode
    phase_name : String
    reason : String
    } derive(Eq,
    Debug
    )

    ForecastReport

    pub(all) struct ForecastReport {
    verdict : String
    peak_users : Int
    peak_traffic_units : Int
    peak_risk_score : Int
    steps : Array[ForecastStep]
    } derive(Eq,
    Debug
    )

    ForecastReport::failure_count

    fn ForecastReport::failure_count(self : ForecastReport) -> Int

    ForecastReport::warning_count

    fn ForecastReport::warning_count(self : ForecastReport) -> Int

    ForecastStep

    pub(all) struct ForecastStep {
    phase_name : String
    minute : Int
    projected_users : Int
    projected_traffic_units : Int
    exposure_bp : Int
    risk_score : Int
    budget_status : Severity
    } derive(Eq,
    Debug
    )

    GateDecision

    pub(all) enum GateDecision {
    Advance
    Hold
    Rollback
    Abort
    } derive(Eq,
    Debug
    )

    GateResult

    pub(all) struct GateResult {
    decision : GateDecision
    severity : Severity
    score : Int
    reasons : Array[String]
    } derive(Eq,
    Debug
    )

    ImpactLevel

    pub(all) enum ImpactLevel {
    ImpactNone
    ImpactLow
    ImpactModerate
    ImpactHigh
    ImpactCritical
    } derive(Eq,
    Debug
    )

    IncidentReport

    pub(all) struct IncidentReport {
    title : String
    minute : Int
    impact : ImpactLevel
    decision : GateDecision
    severity : Severity
    affected_users : Int
    affected_traffic_units : Int
    primary_reason : String
    actions : Array[String]
    } derive(Eq,
    Debug
    )

    IncidentReport::IncidentReport

    fn IncidentReport::IncidentReport(title? : String, minute? : Int, impact? : ImpactLevel, decision? : GateDecision, severity? : Severity, affected_users? : Int, affected_traffic_units? : Int, primary_reason? : String, actions? : Array[String]) -> IncidentReport

    MetricWindow

    pub(all) struct MetricWindow {
    start_minute : Int
    end_minute : Int
    total_samples : Int
    max_error_per_million : Int
    max_latency_p95_ms : Int
    max_saturation_bp : Int
    synthetic_pass_count : Int
    dependency_failures : Int
    } derive(Eq,
    Debug
    )

    MetricsSnapshot

    pub(all) struct MetricsSnapshot {
    minute : Int
    sample_count : Int
    error_per_million : Int
    latency_p95_ms : Int
    saturation_bp : Int
    synthetic_pass : Bool
    dependency_failures : Int
    } derive(Eq,
    Debug
    )

    MetricsSnapshot::MetricsSnapshot

    fn MetricsSnapshot::MetricsSnapshot(minute? : Int, sample_count? : Int, error_per_million? : Int, latency_p95_ms? : Int, saturation_bp? : Int, synthetic_pass? : Bool, dependency_failures? : Int) -> MetricsSnapshot

    PhaseRecommendation

    pub(all) struct PhaseRecommendation {
    next_percent_bp : Int
    next_mode : RolloutMode
    decision : GateDecision
    title : String
    rationale : String
    action_items : Array[String]
    } derive(Eq,
    Debug
    )

    PlanChange

    pub(all) struct PlanChange {
    field : String
    kind : PlanChangeKind
    before : String
    after : String
    severity : Severity
    detail : String
    } derive(Eq,
    Debug
    )

    PlanChange::PlanChange

    fn PlanChange::PlanChange(field? : String, kind? : PlanChangeKind, before? : String, after? : String, severity? : Severity, detail? : String) -> PlanChange

    PlanChangeKind

    pub(all) enum PlanChangeKind {
    ChangeAdded
    ChangeRemoved
    ChangeIncreased
    ChangeDecreased
    ChangeModified
    ChangeUnchanged
    } derive(Eq,
    Debug
    )

    PlanDiff

    pub(all) struct PlanDiff {
    verdict : String
    risk_delta : Int
    changes : Array[PlanChange]
    } derive(Eq,
    Debug
    )

    PlanDiff::to_json

    fn PlanDiff::to_json(self : PlanDiff) -> String

    PlanDiff::to_markdown

    fn PlanDiff::to_markdown(self : PlanDiff) -> String

    Region

    pub(all) enum Region {
    NA
    EU
    APAC
    CN
    LATAM
    MEA
    OtherRegion
    } derive(Eq,
    Debug
    )

    ReleaseEvidence

    pub(all) struct ReleaseEvidence {
    applicant_name : String
    github_owner : String
    repository_name : String
    mooncakes_package : String
    version : String
    ci_status : String
    issue_records : Array[String]
    test_records : Array[String]
    release_notes : String
    design_notes : String
    license : String
    } derive(Eq,
    Debug
    )

    ReleaseEvidence::ReleaseEvidence

    fn ReleaseEvidence::ReleaseEvidence(applicant_name? : String, github_owner? : String, repository_name? : String, mooncakes_package? : String, version? : String, ci_status? : String, issue_records? : Array[String], test_records? : Array[String], release_notes? : String, design_notes? : String, license? : String) -> ReleaseEvidence

    RiskBudget

    pub(all) struct RiskBudget {
    max_error_per_million : Int
    max_latency_p95_ms : Int
    min_samples : Int
    max_saturation_bp : Int
    max_blast_radius_bp : Int
    require_synthetic_pass : Bool
    max_dependency_failures : Int
    } derive(Eq,
    Debug
    )

    RiskBudget::RiskBudget

    fn RiskBudget::RiskBudget(max_error_per_million? : Int, max_latency_p95_ms? : Int, min_samples? : Int, max_saturation_bp? : Int, max_blast_radius_bp? : Int, require_synthetic_pass? : Bool, max_dependency_failures? : Int) -> RiskBudget

    RolloutFinding

    pub(all) struct RolloutFinding {
    id : String
    kind : ValidationKind
    severity : Severity
    title : String
    detail : String
    repair : String
    evidence : String
    penalty : Int
    } derive(Eq,
    Debug
    )

    RolloutMode

    pub(all) enum RolloutMode {
    Shadow
    DryRun
    Live
    KillSwitch
    } derive(Eq,
    Debug
    )

    RolloutPhase

    pub(all) struct RolloutPhase {
    name : String
    start_minute : Int
    duration_minutes : Int
    percent_bp : Int
    mode : RolloutMode
    require_manual_approval : Bool
    } derive(Eq,
    Debug
    )

    RolloutPhase::RolloutPhase

    fn RolloutPhase::RolloutPhase(name? : String, start_minute? : Int, duration_minutes? : Int, percent_bp? : Int, mode? : RolloutMode, require_manual_approval? : Bool) -> RolloutPhase

    RolloutPlan

    pub(all) struct RolloutPlan {
    name : String
    salt : String
    segments : Array[SegmentRule]
    phases : Array[RolloutPhase]
    budget : RiskBudget
    owner : String
    repository : String
    package_name : String
    } derive(Eq,
    Debug
    )

    RolloutPlan::RolloutPlan

    fn RolloutPlan::RolloutPlan(name? : String, salt? : String, segments? : Array[SegmentRule], phases? : Array[RolloutPhase], budget? : RiskBudget, owner? : String, repository? : String, package_name? : String) -> RolloutPlan

    RunbookRecord

    pub(all) struct RunbookRecord {
    id : String
    kind : RunbookRecordKind
    state : RunbookState
    title : String
    evidence : String
    owner : String
    minute : Int
    action : String
    } derive(Eq,
    Debug
    )

    RunbookRecord::RunbookRecord

    fn RunbookRecord::RunbookRecord(id? : String, kind? : RunbookRecordKind, state? : RunbookState, title? : String, evidence? : String, owner? : String, minute? : Int, action? : String) -> RunbookRecord

    RunbookRecordKind

    pub(all) enum RunbookRecordKind {
    OwnerNote
    MetricNote
    RollbackNote
    ApprovalNote
    IncidentNote
    ReleaseNote
    SupportNote
    } derive(Eq,
    Debug
    )

    RunbookReport

    pub(all) struct RunbookReport {
    score : Int
    verdict : String
    records : Array[RunbookRecord]
    findings : Array[RolloutFinding]
    owner_count : Int
    metric_count : Int
    rollback_count : Int
    approval_count : Int
    release_count : Int
    } derive(Eq,
    Debug
    )

    RunbookReport::to_markdown

    fn RunbookReport::to_markdown(self : RunbookReport) -> String

    RunbookState

    pub(all) enum RunbookState {
    Draft
    Ready
    Accepted
    NeedsFollowup
    } derive(Eq,
    Debug
    )

    SegmentRule

    pub(all) struct SegmentRule {
    name : String
    include_regions : Array[Region]
    exclude_regions : Array[Region]
    include_tiers : Array[UserTier]
    exclude_tiers : Array[UserTier]
    require_tags : Array[String]
    exclude_tags : Array[String]
    min_version : String
    max_bucket_bp : Int
    } derive(Eq,
    Debug
    )

    SegmentRule::SegmentRule

    fn SegmentRule::SegmentRule(name? : String, include_regions? : Array[Region], exclude_regions? : Array[Region], include_tiers? : Array[UserTier], exclude_tiers? : Array[UserTier], require_tags? : Array[String], exclude_tags? : Array[String], min_version? : String, max_bucket_bp? : Int) -> SegmentRule

    Severity

    pub(all) enum Severity {
    Pass
    Warn
    Fail
    } derive(Eq,
    Debug
    )

    SimulationReport

    pub(all) struct SimulationReport {
    plan_name : String
    verdict : String
    score : Int
    findings : Array[RolloutFinding]
    steps : Array[SimulationStep]
    final_gate : GateResult
    blast_radius : BlastRadius
    } derive(Eq,
    Debug
    )

    SimulationReport::has_blockers

    fn SimulationReport::has_blockers(self : SimulationReport) -> Bool

    SimulationReport::to_json

    fn SimulationReport::to_json(self : SimulationReport) -> String

    SimulationReport::to_markdown

    fn SimulationReport::to_markdown(self : SimulationReport) -> String

    SimulationReport::warning_count

    fn SimulationReport::warning_count(self : SimulationReport) -> Int

    SimulationStep

    pub(all) struct SimulationStep {
    minute : Int
    phase_name : String
    exposed_users : Int
    exposed_traffic_units : Int
    gate : GateResult
    } derive(Eq,
    Debug
    )

    TimelineEvent

    pub(all) struct TimelineEvent {
    minute : Int
    phase_name : String
    kind : TimelineEventKind
    mode : RolloutMode
    percent_bp : Int
    note : String
    } derive(Eq,
    Debug
    )

    TimelineEvent::TimelineEvent

    fn TimelineEvent::TimelineEvent(minute? : Int, phase_name? : String, kind? : TimelineEventKind, mode? : RolloutMode, percent_bp? : Int, note? : String) -> TimelineEvent

    TimelineEventKind

    pub(all) enum TimelineEventKind {
    EventPhaseStart
    EventPhaseEnd
    EventManualApproval
    EventMetricCheckpoint
    EventRollbackGuard
    } derive(Eq,
    Debug
    )

    TrafficForecast

    pub(all) struct TrafficForecast {
    base_users : Int
    base_traffic_units : Int
    growth_bp_per_hour : Int
    peak_multiplier_bp : Int
    safety_margin_bp : Int
    } derive(Eq,
    Debug
    )

    TrafficForecast::TrafficForecast

    fn TrafficForecast::TrafficForecast(base_users? : Int, base_traffic_units? : Int, growth_bp_per_hour? : Int, peak_multiplier_bp? : Int, safety_margin_bp? : Int) -> TrafficForecast

    TrendDelta

    pub(all) struct TrendDelta {
    sample_delta : Int
    error_delta : Int
    latency_delta_ms : Int
    saturation_delta_bp : Int
    dependency_failure_delta : Int
    } derive(Eq,
    Debug
    )

    UserProfile

    pub(all) struct UserProfile {
    id : String
    region : Region
    tier : UserTier
    version : String
    tags : Array[String]
    staff : Bool
    traffic_units : Int
    } derive(Eq,
    Debug
    )

    UserProfile::UserProfile

    fn UserProfile::UserProfile(id? : String, region? : Region, tier? : UserTier, version? : String, tags? : Array[String], staff? : Bool, traffic_units? : Int) -> UserProfile

    UserTier

    pub(all) enum UserTier {
    Free
    Pro
    Enterprise
    Staff
    Internal
    } derive(Eq,
    Debug
    )

    ValidationKind

    pub(all) enum ValidationKind {
    PlanShape
    SegmentShape
    RiskBudgetShape
    MetricGate
    BlastRadiusGate
    ScheduleGate
    IdentityGate
    } derive(Eq,
    Debug
    )

    active_phase

    fn active_phase(plan : RolloutPlan, minute : Int) -> RolloutPhase?

    all_synthetic_pass

    fn all_synthetic_pass(metrics : Array[MetricsSnapshot]) -> Bool

    blast_radius_for_users

    fn blast_radius_for_users(plan : RolloutPlan, users : Array[UserProfile], minute : Int) -> BlastRadius

    blast_radius_markdown

    fn blast_radius_markdown(blast : BlastRadius) -> String

    canary_playbook_markdown

    fn canary_playbook_markdown() -> String

    canaryring_summary

    fn canaryring_summary() -> String

    change_kind_text

    fn change_kind_text(kind : PlanChangeKind) -> String

    cohort_distribution

    fn cohort_distribution(users : Array[UserProfile], salt : String, ring_count : Int) -> Array[String]

    cohort_label

    fn cohort_label(user_id : String, salt : String, ring_count : Int) -> String

    combine_gates

    fn combine_gates(left : GateResult, right : GateResult) -> GateResult

    compare_plans

    fn compare_plans(base : RolloutPlan, candidate : RolloutPlan) -> PlanDiff

    compare_recommendations

    fn compare_recommendations(left : PhaseRecommendation, right : PhaseRecommendation) -> PhaseRecommendation

    control_domain_text

    fn control_domain_text(domain : ControlDomain) -> String

    count_segment_matches

    fn count_segment_matches(users : Array[UserProfile], rule : SegmentRule, salt? : String) -> Int

    customer_note_draft

    fn customer_note_draft(report : IncidentReport) -> String

    default_control_rules

    fn default_control_rules() -> Array[ControlRule]

    default_traffic_forecast

    fn default_traffic_forecast() -> TrafficForecast

    evaluate_control_rule

    fn evaluate_control_rule(rule : ControlRule, plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> ControlEvaluation

    evaluate_gate

    fn evaluate_gate(budget : RiskBudget, metrics : MetricsSnapshot, blast : BlastRadius) -> GateResult

    example_plan

    fn example_plan() -> RolloutPlan

    example_release_evidence

    fn example_release_evidence() -> ReleaseEvidence

    example_report

    fn example_report() -> SimulationReport

    example_runbook_records

    fn example_runbook_records() -> Array[RunbookRecord]

    example_users

    fn example_users() -> Array[UserProfile]

    explain_bucket

    fn explain_bucket(user : UserProfile, plan : RolloutPlan, minute : Int) -> String

    exposed_traffic_units

    fn exposed_traffic_units(plan : RolloutPlan, users : Array[UserProfile], minute : Int) -> Int

    exposed_user_count

    fn exposed_user_count(plan : RolloutPlan, users : Array[UserProfile], minute : Int) -> Int

    exposed_users

    fn exposed_users(plan : RolloutPlan, users : Array[UserProfile], minute : Int) -> Array[UserProfile]

    exposure_for_user

    fn exposure_for_user(plan : RolloutPlan, user : UserProfile, minute : Int) -> ExposureDecision

    failing_metrics

    fn failing_metrics() -> Array[MetricsSnapshot]

    failure_scenario_catalog_markdown

    fn failure_scenario_catalog_markdown() -> String

    forecast_json

    fn forecast_json(report : ForecastReport) -> String

    forecast_markdown

    fn forecast_markdown(report : ForecastReport) -> String

    forecast_rollout

    fn forecast_rollout(plan : RolloutPlan, forecast : TrafficForecast) -> ForecastReport

    healthy_metrics

    fn healthy_metrics() -> Array[MetricsSnapshot]

    impact_text

    fn impact_text(impact : ImpactLevel) -> String

    in_percent_bucket

    fn in_percent_bucket(user_id : String, salt : String, percent_bp : Int) -> Bool

    incident_report

    fn incident_report(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot], minute : Int) -> IncidentReport

    incident_report_json

    fn incident_report_json(report : IncidentReport) -> String

    incident_report_markdown

    fn incident_report_markdown(report : IncidentReport) -> String

    incident_requires_customer_note

    fn incident_requires_customer_note(report : IncidentReport) -> Bool

    incident_score

    fn incident_score(report : IncidentReport) -> Int

    last_phase

    fn last_phase(plan : RolloutPlan) -> RolloutPhase?

    latest_metric

    fn latest_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot

    matches_any_segment

    fn matches_any_segment(user : UserProfile, segments : Array[SegmentRule], salt? : String) -> Bool

    matching_segment_names

    fn matching_segment_names(user : UserProfile, segments : Array[SegmentRule], salt? : String) -> Array[String]

    max_error_metric

    fn max_error_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot

    max_latency_metric

    fn max_latency_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot

    max_phase_percent

    fn max_phase_percent(plan : RolloutPlan) -> Int

    max_saturation_metric

    fn max_saturation_metric(metrics : Array[MetricsSnapshot]) -> MetricsSnapshot

    metric_trend_gate

    fn metric_trend_gate(budget : RiskBudget, previous : MetricsSnapshot, current : MetricsSnapshot, blast : BlastRadius) -> GateResult

    metric_window

    fn metric_window(metrics : Array[MetricsSnapshot], start_minute : Int, end_minute : Int) -> MetricWindow

    metric_window_gate

    fn metric_window_gate(budget : RiskBudget, previous : MetricWindow, current : MetricWindow, blast : BlastRadius) -> GateResult

    mode_text

    fn mode_text(mode : RolloutMode) -> String

    next_timeline_event

    fn next_timeline_event(events : Array[TimelineEvent], minute : Int) -> TimelineEvent?

    phase_is_monotonic

    fn phase_is_monotonic(previous : RolloutPhase, current : RolloutPhase) -> Bool

    phase_minutes_by_mode

    fn phase_minutes_by_mode(plan : RolloutPlan, mode : RolloutMode) -> Int

    phase_overlap

    fn phase_overlap(a : RolloutPhase, b : RolloutPhase) -> Bool

    plan_diff_risk_delta

    fn plan_diff_risk_delta(changes : Array[PlanChange]) -> Int

    plan_duration

    fn plan_duration(plan : RolloutPlan) -> Int

    plan_report_json

    fn plan_report_json(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> String

    plan_report_markdown

    fn plan_report_markdown(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> String

    playbook_rule_count

    fn playbook_rule_count() -> Int

    recommend_next_phase

    fn recommend_next_phase(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot], minute : Int) -> PhaseRecommendation

    recommendation_markdown

    fn recommendation_markdown(recommendation : PhaseRecommendation) -> String

    release_evidence_review

    fn release_evidence_review(evidence : ReleaseEvidence) -> EvidenceReport

    resubmission_boundary_markdown

    fn resubmission_boundary_markdown() -> String

    review_controls

    fn review_controls(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> ControlReview

    rollout_bucket

    fn rollout_bucket(user_id : String, salt : String) -> Int

    rollout_timeline

    fn rollout_timeline(plan : RolloutPlan) -> Array[TimelineEvent]

    runbook_review

    fn runbook_review(records : Array[RunbookRecord]) -> RunbookReport

    scenario_count

    fn scenario_count() -> Int

    segment_matches

    fn segment_matches(user : UserProfile, rule : SegmentRule, salt? : String) -> Bool

    segment_summary

    fn segment_summary(rule : SegmentRule) -> String

    simulate_rollout

    fn simulate_rollout(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> SimulationReport

    simulation_json

    fn simulation_json(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> String

    simulation_markdown

    fn simulation_markdown(plan : RolloutPlan, users : Array[UserProfile], metrics : Array[MetricsSnapshot]) -> String

    split_bucket

    fn split_bucket(bucket_bp : Int) -> String

    stable_hash

    fn stable_hash(text : String) -> Int

    stable_sample_key

    fn stable_sample_key(user : UserProfile, salt : String) -> String

    support_handoff_minutes

    fn support_handoff_minutes(plan : RolloutPlan, max_shift_minutes : Int) -> Array[Int]

    timeline_event_count_by_kind

    fn timeline_event_count_by_kind(events : Array[TimelineEvent], kind : TimelineEventKind) -> Int

    timeline_event_text

    fn timeline_event_text(kind : TimelineEventKind) -> String

    timeline_has_manual_gate

    fn timeline_has_manual_gate(plan : RolloutPlan) -> Bool

    timeline_json

    fn timeline_json(plan : RolloutPlan) -> String

    timeline_markdown

    fn timeline_markdown(plan : RolloutPlan) -> String

    total_dependency_failures

    fn total_dependency_failures(metrics : Array[MetricsSnapshot]) -> Int

    total_traffic

    fn total_traffic(users : Array[UserProfile]) -> Int

    traffic_weighted_bucket

    fn traffic_weighted_bucket(user : UserProfile, salt : String) -> Int

    trend_delta

    fn trend_delta(previous : MetricWindow, current : MetricWindow) -> TrendDelta

    validate_plan

    fn validate_plan(plan : RolloutPlan) -> Array[RolloutFinding]

    window_json

    fn window_json(window : MetricWindow) -> String

    window_markdown

    fn window_markdown(window : MetricWindow) -> String

    window_to_snapshot

    fn window_to_snapshot(window : MetricWindow) -> MetricsSnapshot