///|
pub(all) enum ControlDomain {
OwnerEvidence
SegmentEvidence
ScheduleEvidence
MetricEvidence
BlastEvidence
RollbackEvidence
ReleaseDomain
} derive(Eq, Debug)
///|
pub(all) struct ControlRule {
id : String
domain : ControlDomain
title : String
check_text : String
evidence_text : String
repair_text : String
weight : Int
} derive(Eq, Debug)
///|
pub fn ControlRule::ControlRule(
id? : String = "",
domain? : ControlDomain = OwnerEvidence,
title? : String = "",
check_text? : String = "",
evidence_text? : String = "",
repair_text? : String = "",
weight? : Int = 1,
) -> ControlRule {
{ id, domain, title, check_text, evidence_text, repair_text, weight }
}
///|
pub(all) struct ControlEvaluation {
rule_id : String
domain : ControlDomain
status : Severity
score : Int
title : String
message : String
repair : String
} derive(Eq, Debug)
///|
pub(all) struct ControlReview {
verdict : String
score : Int
passed : Int
warned : Int
failed : Int
evaluations : Array[ControlEvaluation]
} derive(Eq, Debug)
///|
pub fn default_control_rules() -> Array[ControlRule] {
[
ControlRule(
id="POL-001",
domain=OwnerEvidence,
title="Repository owner is fixed",
check_text="The rollout plan points to one repository owner.",
evidence_text="owner/repository/package fields",
repair_text="Use one GitHub owner and one Mooncakes namespace.",
weight=3,
),
ControlRule(
id="POL-002",
domain=OwnerEvidence,
title="Package namespace is aligned",
check_text="The package name uses the same namespace as the submitted account.",
evidence_text="moon.mod package name",
repair_text="Set moon.mod name before publishing.",
weight=4,
),
ControlRule(
id="POL-003",
domain=OwnerEvidence,
title="Repository URL is reviewer friendly",
check_text="The repository URL is concrete and does not rely on local credentials.",
evidence_text="plan.repository",
repair_text="Use an HTTPS GitHub URL that can be opened publicly.",
weight=3,
),
ControlRule(
id="POL-004",
domain=OwnerEvidence,
title="Runtime output avoids private contact details",
check_text="Library examples should not print phone numbers or personal email addresses.",
evidence_text="public package output",
repair_text="Keep personal contact details in the separate proposal only.",
weight=2,
),
ControlRule(
id="POL-005",
domain=OwnerEvidence,
title="One submission identity is used",
check_text="Evidence should not mix multiple repository owners or package owners.",
evidence_text="owner field and package namespace",
repair_text="Replace stale owners before committing.",
weight=4,
),
ControlRule(
id="POL-006",
domain=SegmentEvidence,
title="At least one target segment exists",
check_text="A canary plan should define who can enter the rollout.",
evidence_text="plan.segments",
repair_text="Add a named SegmentRule.",
weight=3,
),
ControlRule(
id="POL-007",
domain=SegmentEvidence,
title="Segments match real sample users",
check_text="The example population should exercise the segment rules.",
evidence_text="count_segment_matches",
repair_text="Adjust fixtures or segment rules so tests cover selected users.",
weight=4,
),
ControlRule(
id="POL-008",
domain=SegmentEvidence,
title="High-risk users can be excluded",
check_text="Segment rules should support exclusion tags or exclusion tiers.",
evidence_text="exclude_tags/exclude_tiers",
repair_text="Add exclusion boundaries for VIP or unsupported traffic.",
weight=3,
),
ControlRule(
id="POL-009",
domain=SegmentEvidence,
title="Minimum version is modeled",
check_text="Protocol-sensitive rollouts should be able to require a client version.",
evidence_text="min_version",
repair_text="Set min_version on at least one segment when protocol behavior changes.",
weight=2,
),
ControlRule(
id="POL-010",
domain=SegmentEvidence,
title="Bucket prefilter is available",
check_text="Segments can cap membership before phase percentage is applied.",
evidence_text="max_bucket_bp",
repair_text="Use max_bucket_bp for staff rings or small seed rings.",
weight=2,
),
ControlRule(
id="POL-011",
domain=ScheduleEvidence,
title="Phase windows are non-overlapping",
check_text="Only one rollout phase should be active at a given minute.",
evidence_text="phase_overlap",
repair_text="Move start_minute or duration_minutes to avoid overlaps.",
weight=5,
),
ControlRule(
id="POL-012",
domain=ScheduleEvidence,
title="Phase durations are positive",
check_text="Every phase must have time to collect evidence.",
evidence_text="duration_minutes",
repair_text="Use duration_minutes greater than zero.",
weight=4,
),
ControlRule(
id="POL-013",
domain=ScheduleEvidence,
title="Large live phases need approval",
check_text="At least 50% live exposure should require a manual checkpoint.",
evidence_text="require_manual_approval",
repair_text="Mark large live phases as manually approved.",
weight=4,
),
ControlRule(
id="POL-014",
domain=ScheduleEvidence,
title="Shadow and dry-run phases are represented",
check_text="The schedule should contain a low-risk phase before live traffic.",
evidence_text="mode values",
repair_text="Add Shadow or DryRun phases before Live phases.",
weight=3,
),
ControlRule(
id="POL-015",
domain=ScheduleEvidence,
title="Phase percentages are bounded",
check_text="Each phase uses basis points inside 0..10000.",
evidence_text="percent_bp",
repair_text="Clamp percent_bp to the allowed range.",
weight=4,
),
ControlRule(
id="POL-016",
domain=MetricEvidence,
title="Metric samples exist",
check_text="Gate decisions require at least one MetricsSnapshot.",
evidence_text="metrics array",
repair_text="Record synthetic or production metric snapshots.",
weight=4,
),
ControlRule(
id="POL-017",
domain=MetricEvidence,
title="Error budget is enforced",
check_text="The gate compares error_per_million against the budget.",
evidence_text="evaluate_gate",
repair_text="Lower exposure or roll back when error rate exceeds budget.",
weight=5,
),
ControlRule(
id="POL-018",
domain=MetricEvidence,
title="Latency budget is enforced",
check_text="The gate compares latency_p95_ms against the budget.",
evidence_text="evaluate_gate",
repair_text="Hold or roll back when latency exceeds the p95 ceiling.",
weight=4,
),
ControlRule(
id="POL-019",
domain=MetricEvidence,
title="Synthetic checks are honored",
check_text="Synthetic failures can block an unsafe advance.",
evidence_text="synthetic_pass",
repair_text="Keep require_synthetic_pass true for critical paths.",
weight=4,
),
ControlRule(
id="POL-020",
domain=MetricEvidence,
title="Dependency failure budget exists",
check_text="Dependency failures are part of the release gate.",
evidence_text="max_dependency_failures",
repair_text="Set a strict dependency failure budget.",
weight=3,
),
ControlRule(
id="POL-021",
domain=BlastEvidence,
title="User blast radius is computed",
check_text="Reports include exposed user count and user share.",
evidence_text="blast_radius_for_users",
repair_text="Provide the user population fixture for simulation.",
weight=4,
),
ControlRule(
id="POL-022",
domain=BlastEvidence,
title="Traffic blast radius is computed",
check_text="High-volume users should increase exposure risk.",
evidence_text="traffic_units",
repair_text="Set realistic traffic_units on sample users.",
weight=4,
),
ControlRule(
id="POL-023",
domain=BlastEvidence,
title="Region spread is tracked",
check_text="Reports count how many regions are touched.",
evidence_text="region_count",
repair_text="Keep region coverage visible in blast reports.",
weight=3,
),
ControlRule(
id="POL-024",
domain=BlastEvidence,
title="Tier spread is tracked",
check_text="Reports count how many account tiers are touched.",
evidence_text="tier_count",
repair_text="Keep tier coverage visible in blast reports.",
weight=3,
),
ControlRule(
id="POL-025",
domain=BlastEvidence,
title="Blast budget can stop release",
check_text="The gate should hold or roll back when blast exceeds budget.",
evidence_text="max_blast_radius_bp",
repair_text="Lower percent_bp or tighten segments.",
weight=5,
),
ControlRule(
id="POL-026",
domain=RollbackEvidence,
title="Rollback path is explicit",
check_text="The plan should contain a kill switch or documented rollback action.",
evidence_text="KillSwitch phase or incident actions",
repair_text="Add a KillSwitch phase or keep a generated rollback report.",
weight=4,
),
ControlRule(
id="POL-027",
domain=RollbackEvidence,
title="Hold decision blocks auto advance",
check_text="Warnings should not silently advance to the next live ring.",
evidence_text="GateDecision Hold",
repair_text="Require manual review before advance after warning.",
weight=3,
),
ControlRule(
id="POL-028",
domain=RollbackEvidence,
title="Rollback reasons are printable",
check_text="Generated reports should include human-readable gate reasons.",
evidence_text="GateResult.reasons",
repair_text="Keep reason strings attached to every gate.",
weight=3,
),
ControlRule(
id="POL-029",
domain=RollbackEvidence,
title="Rollback preserves previous healthy window",
check_text="Trend gates should compare previous and current snapshots.",
evidence_text="metric_trend_gate",
repair_text="Provide adjacent metric windows.",
weight=3,
),
ControlRule(
id="POL-030",
domain=RollbackEvidence,
title="Failure output is actionable",
check_text="Incident reports should include operator actions.",
evidence_text="incident actions",
repair_text="Use incident_report_markdown for failed simulations.",
weight=3,
),
ControlRule(
id="POL-031",
domain=ReleaseDomain,
title="README can cite runnable examples",
check_text="The package should expose a summary and markdown report for examples.",
evidence_text="canaryring_summary and plan_report_markdown",
repair_text="Keep examples/basic and cmd/main executable.",
weight=3,
),
ControlRule(
id="POL-032",
domain=ReleaseDomain,
title="Simulation exports JSON",
check_text="Automation can consume machine-readable reports.",
evidence_text="SimulationReport::to_json",
repair_text="Keep JSON fields stable across patch releases.",
weight=3,
),
ControlRule(
id="POL-033",
domain=ReleaseDomain,
title="Simulation exports Markdown",
check_text="Reviewers can inspect results without custom tooling.",
evidence_text="SimulationReport::to_markdown",
repair_text="Keep Markdown report deterministic.",
weight=3,
),
ControlRule(
id="POL-034",
domain=ReleaseDomain,
title="Release evidence review exists",
check_text="Package metadata can be checked before final acceptance.",
evidence_text="release_evidence_review",
repair_text="Run release evidence review before publishing.",
weight=3,
),
ControlRule(
id="POL-035",
domain=ReleaseDomain,
title="Runbook review exists",
check_text="Operational notes can be reviewed with deterministic rules.",
evidence_text="runbook_review",
repair_text="Keep runbook records in docs/test-record.md.",
weight=2,
),
]
}
///|
pub fn control_domain_text(domain : ControlDomain) -> String {
match domain {
OwnerEvidence => "owner"
SegmentEvidence => "segment"
ScheduleEvidence => "schedule"
MetricEvidence => "metric"
BlastEvidence => "blast"
RollbackEvidence => "rollback"
ReleaseDomain => "release"
}
}
///|
pub fn evaluate_control_rule(
rule : ControlRule,
plan : RolloutPlan,
users : Array[UserProfile],
metrics : Array[MetricsSnapshot],
) -> ControlEvaluation {
let status = domain_status(rule.domain, plan, users, metrics)
let score = if status == Pass {
rule.weight
} else if status == Warn {
rule.weight / 2
} else {
0
}
{
rule_id: rule.id,
domain: rule.domain,
status,
score,
title: rule.title,
message: rule.check_text + " Evidence: " + rule.evidence_text,
repair: if status == Pass {
""
} else {
rule.repair_text
},
}
}
///|
pub fn review_controls(
plan : RolloutPlan,
users : Array[UserProfile],
metrics : Array[MetricsSnapshot],
) -> ControlReview {
let rules = default_control_rules()
let evaluations = Array::new(capacity=rules.length())
let mut possible = 0
let mut earned = 0
let mut passed = 0
let mut warned = 0
let mut failed = 0
for rule in rules {
let evaluation = evaluate_control_rule(rule, plan, users, metrics)
possible += rule.weight
earned += evaluation.score
if evaluation.status == Pass {
passed += 1
} else if evaluation.status == Warn {
warned += 1
} else {
failed += 1
}
evaluations.push(evaluation)
}
let score = if possible == 0 { 0 } else { earned * 100 / possible }
let verdict = if failed > 0 {
"blocked"
} else if warned > 0 || score < 90 {
"review"
} else {
"ready"
}
{ verdict, score, passed, warned, failed, evaluations }
}
///|
fn domain_status(
domain : ControlDomain,
plan : RolloutPlan,
users : Array[UserProfile],
metrics : Array[MetricsSnapshot],
) -> Severity {
match domain {
OwnerEvidence => owner_control_status(plan)
SegmentEvidence => segment_control_status(plan, users)
ScheduleEvidence => schedule_control_status(plan)
MetricEvidence => metric_control_status(plan, users, metrics)
BlastEvidence => blast_control_status(plan, users)
RollbackEvidence => rollback_control_status(plan, metrics)
ReleaseDomain => release_control_status(plan)
}
}
///|
fn owner_control_status(plan : RolloutPlan) -> Severity {
if plan.owner == "EJJ-ai-nb" &&
plan.package_name == "EJJ-ai-nb/canaryring" &&
contains_ci(plan.repository, "github.com/EJJ-ai-nb/canaryring") {
Pass
} else if has_text(plan.owner) &&
has_text(plan.package_name) &&
has_text(plan.repository) {
Warn
} else {
Fail
}
}
///|
fn segment_control_status(
plan : RolloutPlan,
users : Array[UserProfile],
) -> Severity {
if plan.segments.length() == 0 {
return Warn
}
let mut matched = 0
let mut has_exclusion = false
let mut has_version = false
let mut has_prefilter = false
for segment in plan.segments {
matched += count_segment_matches(users, segment, salt=plan.salt)
if segment.exclude_tags.length() > 0 ||
segment.exclude_tiers.length() > 0 ||
segment.exclude_regions.length() > 0 {
has_exclusion = true
}
if has_text(segment.min_version) {
has_version = true
}
if segment.max_bucket_bp < 10000 {
has_prefilter = true
}
}
if matched == 0 {
Fail
} else if has_exclusion && has_version && has_prefilter {
Pass
} else {
Warn
}
}
///|
fn schedule_control_status(plan : RolloutPlan) -> Severity {
let findings = validate_plan(plan)
let mut has_schedule_fail = false
let mut has_live = false
let mut has_safe_phase = false
for finding in findings {
if finding.kind == ScheduleGate && finding.severity == Fail {
has_schedule_fail = true
}
}
for phase in plan.phases {
if phase.mode == Live {
has_live = true
}
if phase.mode == Shadow || phase.mode == DryRun {
has_safe_phase = true
}
}
if has_schedule_fail || plan.phases.length() == 0 {
Fail
} else if has_live && has_safe_phase {
Pass
} else {
Warn
}
}
///|
fn metric_control_status(
plan : RolloutPlan,
users : Array[UserProfile],
metrics : Array[MetricsSnapshot],
) -> Severity {
if metrics.length() == 0 {
return Fail
}
let minute = plan_duration(plan) - 1
let blast = blast_radius_for_users(plan, users, minute)
let gate = evaluate_gate(plan.budget, nearest_metric(metrics, minute), blast)
gate.severity
}
///|
fn blast_control_status(
plan : RolloutPlan,
users : Array[UserProfile],
) -> Severity {
if users.length() == 0 {
return Fail
}
let minute = plan_duration(plan) - 1
let blast = blast_radius_for_users(plan, users, minute)
if blast.percent_bp <= plan.budget.max_blast_radius_bp {
Pass
} else if blast.percent_bp <= plan.budget.max_blast_radius_bp + 1500 {
Warn
} else {
Fail
}
}
///|
fn rollback_control_status(
plan : RolloutPlan,
metrics : Array[MetricsSnapshot],
) -> Severity {
let mut has_kill = false
let mut has_manual_large_live = false
for phase in plan.phases {
if phase.mode == KillSwitch {
has_kill = true
}
if phase.mode == Live &&
phase.percent_bp >= 5000 &&
phase.require_manual_approval {
has_manual_large_live = true
}
}
if has_kill {
Pass
} else if has_manual_large_live && metrics.length() > 1 {
Warn
} else {
Fail
}
}
///|
fn release_control_status(plan : RolloutPlan) -> Severity {
if plan.package_name == "EJJ-ai-nb/canaryring" &&
contains_ci(plan.repository, "github.com/EJJ-ai-nb/canaryring") &&
has_text(plan.name) {
Pass
} else if has_text(plan.package_name) && has_text(plan.repository) {
Warn
} else {
Fail
}
}
///|
fn nearest_metric(
metrics : Array[MetricsSnapshot],
minute : Int,
) -> MetricsSnapshot {
let mut chosen = MetricsSnapshot::MetricsSnapshot(minute~)
let mut best_delta = 2147483
for metric in metrics {
let delta = if metric.minute > minute {
metric.minute - minute
} else {
minute - metric.minute
}
if delta < best_delta {
chosen = metric
best_delta = delta
}
}
chosen
}
///|
pub fn ControlReview::to_markdown(self : ControlReview) -> String {
let out = StringBuilder()
out.write_string("# CanaryRing Control Review\n\n")
out.write_string("- Verdict: " + self.verdict + "\n")
out.write_string("- Score: " + self.score.to_string() + "/100\n")
out.write_string("- Passed: " + self.passed.to_string() + "\n")
out.write_string("- Warnings: " + self.warned.to_string() + "\n")
out.write_string("- Failed: " + self.failed.to_string() + "\n\n")
for item in self.evaluations {
out.write_string("- [" + severity_text(item.status) + "] ")
out.write_string(
item.rule_id + " " + control_domain_text(item.domain) + " ",
)
out.write_string(item.title)
if item.repair != "" {
out.write_string(" Fix: " + item.repair)
}
out.write_string("\n")
}
out.to_string()
}
///|
pub fn ControlReview::to_json(self : ControlReview) -> String {
let out = StringBuilder()
out.write_string("{")
out.write_string("\"verdict\":\"" + json_escape(self.verdict) + "\",")
out.write_string("\"score\":" + self.score.to_string() + ",")
out.write_string("\"passed\":" + self.passed.to_string() + ",")
out.write_string("\"warned\":" + self.warned.to_string() + ",")
out.write_string("\"failed\":" + self.failed.to_string() + ",")
out.write_string("\"evaluations\":[")
for i in 0.. 0 {
out.write_string(",")
}
out.write_string("{")
out.write_string("\"id\":\"" + json_escape(item.rule_id) + "\",")
out.write_string("\"domain\":\"" + control_domain_text(item.domain) + "\",")
out.write_string("\"status\":\"" + severity_text(item.status) + "\",")
out.write_string("\"score\":" + item.score.to_string())
out.write_string("}")
}
out.write_string("]}")
out.to_string()
}