///|
/// Environments describe the operational blast radius of a configuration
/// change. They are deliberately independent from a deployment platform so
/// the same plan can be reviewed locally, in CI, or by a service launcher.
pub(all) enum ReleaseEnvironment {
Development
Staging
Canary
Production
} derive(Debug, Eq)
///|
pub fn ReleaseEnvironment::name(self : ReleaseEnvironment) -> String {
match self {
Development => "development"
Staging => "staging"
Canary => "canary"
Production => "production"
}
}
///|
pub fn ReleaseEnvironment::requires_approval(self : ReleaseEnvironment) -> Bool {
self is (Canary | Production)
}
///|
pub fn ReleaseEnvironment::maximum_risk(
self : ReleaseEnvironment,
) -> ReleaseRisk {
match self {
Development => Medium
Staging => Medium
Canary => High
Production => Critical
}
}
///|
pub(all) enum ReleaseRisk {
Low
Medium
High
Critical
} derive(Debug, Eq)
///|
pub fn ReleaseRisk::name(self : ReleaseRisk) -> String {
match self {
Low => "low"
Medium => "medium"
High => "high"
Critical => "critical"
}
}
///|
pub fn ReleaseRisk::rank(self : ReleaseRisk) -> Int {
match self {
Low => 0
Medium => 1
High => 2
Critical => 3
}
}
///|
pub fn ReleaseRisk::at_least(self : ReleaseRisk, other : ReleaseRisk) -> Bool {
self.rank() >= other.rank()
}
///|
pub(all) enum ReleaseApproval {
Pending
Approved(reviewer~ : String)
Rejected(reviewer~ : String, reason~ : String)
} derive(Debug, Eq)
///|
pub fn ReleaseApproval::is_approved(self : ReleaseApproval) -> Bool {
self is Approved(_)
}
///|
pub fn ReleaseApproval::label(self : ReleaseApproval) -> String {
match self {
Pending => "pending"
Approved(reviewer~) => "approved by " + reviewer
Rejected(reviewer~, reason~) => "rejected by " + reviewer + ": " + reason
}
}
///|
/// A small, deterministic record for explaining why a release was classified
/// as risky. The message is intentionally short enough for CI annotations.
pub(all) struct ReleaseChange {
key : String
kind : String
risk : ReleaseRisk
message : String
} derive(Debug, Eq)
///|
pub fn ReleaseChange::summary(self : ReleaseChange) -> String {
self.risk.name() + " [" + self.kind + "] " + self.key + ": " + self.message
}
///|
pub(all) struct ReleaseMetadata {
owner : String
change_ticket : String
environment : ReleaseEnvironment
} derive(Debug, Eq)
///|
pub fn release_metadata(
owner : String,
change_ticket : String,
environment : ReleaseEnvironment,
) -> ReleaseMetadata {
{ owner, change_ticket, environment }
}
///|
pub fn ReleaseMetadata::errors(self : ReleaseMetadata) -> Array[String] {
let errors = []
if self.owner.trim() == "" {
errors.push("owner_required")
}
if self.change_ticket.trim() == "" {
errors.push("change_ticket_required")
}
errors
}
///|
pub(all) struct ReleasePlan {
metadata : ReleaseMetadata
next : Provider
previous : Provider?
health : ProviderHealth
compatibility : CompatibilityReport?
changes : Array[ReleaseChange]
risk : ReleaseRisk
approval : ReleaseApproval
allowed : Bool
} derive(Debug)
///|
fn change_risk(environment : ReleaseEnvironment, kind : String) -> ReleaseRisk {
let base = match kind {
"added" => Low
"changed" => High
"removed" => Critical
_ => Medium
}
if environment is Production && base is Low {
Medium
} else {
base
}
}
///|
fn changes_from_report(
report : CompatibilityReport,
environment : ReleaseEnvironment,
) -> Array[ReleaseChange] {
let result = []
for key in report.diff.added {
result.push({
key,
kind: "added",
risk: change_risk(environment, "added"),
message: "new flag is introduced",
})
}
for key in report.diff.changed {
result.push({
key,
kind: "changed",
risk: change_risk(environment, "changed"),
message: "existing flag definition changes",
})
}
for key in report.diff.removed {
result.push({
key,
kind: "removed",
risk: change_risk(environment, "removed"),
message: "existing flag is removed",
})
}
result
}
///|
fn highest_risk(
environment : ReleaseEnvironment,
health : ProviderHealth,
changes : Array[ReleaseChange],
metadata_errors : Array[String],
) -> ReleaseRisk {
let mut risk = Low
if health.status_text() == "warning" {
risk = Medium
}
if !health.is_ready() {
risk = Critical
}
for change in changes {
if change.risk.at_least(risk) {
risk = change.risk
}
}
if metadata_errors.length() > 0 {
risk = Critical
}
if environment is Production && risk is Low {
Medium
} else {
risk
}
}
///|
/// Builds a reviewable release plan. The function does not activate anything;
/// callers must explicitly approve the returned plan and perform activation.
pub fn plan_release(
previous : Provider?,
next : Provider,
environment~ : ReleaseEnvironment,
policy~ : ProviderPolicy,
owner~ : String,
change_ticket~ : String,
) -> ReleasePlan {
let metadata = release_metadata(owner, change_ticket, environment)
let health = next.health_check(policy)
let compatibility = previous.map(old => compare_providers(old, next))
let changes = match compatibility {
Some(report) => changes_from_report(report, environment)
None =>
next
.definitions()
.map(flag => {
key: flag.key,
kind: "initial",
risk: change_risk(environment, "changed"),
message: "initial provider configuration",
})
}
let risk = highest_risk(environment, health, changes, metadata.errors())
let blocked_by_compatibility = match compatibility {
Some(report) => report.requires_replay()
None => false
}
let allowed = health.is_ready() &&
health.policy.is_compliant() &&
!blocked_by_compatibility &&
metadata.errors().length() == 0
let approval = if allowed && !environment.requires_approval() && risk is Low {
Approved(reviewer="automatic")
} else {
Pending
}
{
metadata,
next,
previous,
health,
compatibility,
changes,
risk,
approval,
allowed,
}
}
///|
pub fn ReleasePlan::metadata_errors(self : ReleasePlan) -> Array[String] {
self.metadata.errors()
}
///|
pub fn ReleasePlan::risk(self : ReleasePlan) -> ReleaseRisk {
self.risk
}
///|
pub fn ReleasePlan::environment(self : ReleasePlan) -> ReleaseEnvironment {
self.metadata.environment
}
///|
pub fn ReleasePlan::changes(self : ReleasePlan) -> Array[ReleaseChange] {
self.changes
}
///|
pub fn ReleasePlan::is_approved(self : ReleasePlan) -> Bool {
self.approval.is_approved()
}
///|
pub fn ReleasePlan::requires_manual_approval(self : ReleasePlan) -> Bool {
!self.allowed ||
self.risk.at_least(self.metadata.environment.maximum_risk()) ||
!self.approval.is_approved()
}
///|
pub fn ReleasePlan::approve(
self : ReleasePlan,
reviewer : String,
) -> ReleasePlan {
if reviewer.trim() == "" || !self.allowed {
self
} else {
{ ..self, approval: Approved(reviewer~) }
}
}
///|
pub fn ReleasePlan::reject(
self : ReleasePlan,
reviewer : String,
reason : String,
) -> ReleasePlan {
{ ..self, approval: Rejected(reviewer~, reason~), allowed: false }
}
///|
pub fn ReleasePlan::is_release_ready(self : ReleasePlan) -> Bool {
self.allowed &&
self.approval.is_approved() &&
self.metadata_errors().length() == 0
}
///|
pub fn ReleasePlan::rollback(self : ReleasePlan) -> Array[String] {
match self.previous {
Some(old) =>
[
"restore provider fingerprint " + old.fingerprint(),
"replay acceptance scenarios before reactivation",
"record rollback ticket " + self.metadata.change_ticket,
]
None =>
[
"disable newly introduced flags",
"keep the current provider available for incident review",
"record rollback ticket " + self.metadata.change_ticket,
]
}
}
///|
pub fn ReleasePlan::summary(self : ReleasePlan) -> String {
let state = if self.is_release_ready() { "ready" } else { "blocked" }
state +
" " +
self.metadata.environment.name() +
" release; risk=" +
self.risk.name() +
", changes=" +
self.changes.length().to_string() +
", approval=" +
self.approval.label()
}
///|
pub fn ReleasePlan::render(self : ReleasePlan) -> String {
let lines = [
self.summary(),
"owner=" + self.metadata.owner + ", ticket=" + self.metadata.change_ticket,
"health=" + self.health.status_text(),
]
match self.compatibility {
Some(report) => lines.push("compatibility=" + report.summary())
None => lines.push("compatibility=initial release")
}
for change in self.changes {
lines.push("- " + change.summary())
}
lines.push("rollback:")
for step in self.rollback() {
lines.push(" - " + step)
}
lines.join("\n")
}
///|
pub fn ReleasePlan::decision(self : ReleasePlan) -> DeploymentDecision {
deployment_check(self.previous, self.next, default_policy())
}
///|
pub fn ReleasePlan::approval_label(self : ReleasePlan) -> String {
self.approval.label()
}
///|
pub fn ReleasePlan::change_count(self : ReleasePlan) -> Int {
self.changes.length()
}
///|
pub fn ReleasePlan::risk_explanation(self : ReleasePlan) -> String {
if self.changes.length() == 0 {
"no flag changes; risk comes from the release environment"
} else {
let reasons = []
for change in self.changes {
reasons.push(change.kind + " " + change.key)
}
"risk is driven by " + reasons.join(", ")
}
}
///|
/// Returns operator-facing steps without performing them. This is suitable for
/// a change ticket or a deployment bot's dry-run output.
pub fn ReleasePlan::approval_steps(self : ReleasePlan) -> Array[String] {
let steps = [
"review provider health and policy report", "review compatibility changes and acceptance replay",
]
if self.environment().requires_approval() {
steps.push("obtain explicit approval for " + self.environment().name())
}
if self.risk.at_least(High) {
steps.push("confirm rollback owner and rollback ticket")
}
steps
}
///|
pub fn ReleasePlan::has_breaking_change(self : ReleasePlan) -> Bool {
for change in self.changes {
if change.kind == "removed" {
return true
}
}
false
}
///|
pub fn ReleasePlan::changed_keys(self : ReleasePlan) -> Array[String] {
let keys = []
for change in self.changes {
keys.push(change.key)
}
keys.sort()
keys
}
///|
pub fn ReleasePlan::audit_ready(self : ReleasePlan) -> Bool {
self.metadata_errors().length() == 0 &&
self.health.is_ready() &&
self.changes.length() >= 0
}
///|
pub fn ReleasePlan::risk_score(self : ReleasePlan) -> Int {
10 +
self.risk.rank() * 25 +
(if self.has_breaking_change() { 25 } else { 0 }) +
(if self.metadata_errors().length() > 0 { 50 } else { 0 })
}
///|
pub fn ReleasePlan::guardrails(self : ReleasePlan) -> Array[String] {
let guards = [
"run moon check and moon test before activation", "record provider fingerprint in the deployment log",
]
if self.has_breaking_change() {
guards.push("require compatibility replay before approval")
}
if self.environment() is Production {
guards.push("start with a canary observation window")
}
guards
}
///|
pub fn ReleasePlan::replay_required(self : ReleasePlan) -> Bool {
match self.compatibility {
Some(report) => report.requires_replay()
None => false
}
}
///|
pub fn ReleasePlan::stage(self : ReleasePlan) -> String {
if self.metadata_errors().length() > 0 {
"metadata-invalid"
} else if !self.health.is_ready() {
"health-blocked"
} else if self.replay_required() {
"replay-required"
} else if !self.is_approved() {
self.environment().name() + "-pending-approval"
} else {
self.environment().name() + "-ready"
}
}
///|
pub fn ReleasePlan::change_summary(self : ReleasePlan) -> String {
if self.changes.length() == 0 {
"no changes"
} else {
let mut added = 0
let mut changed = 0
let mut removed = 0
for item in self.changes {
match item.kind {
"added" => added += 1
"changed" => changed += 1
"removed" => removed += 1
_ => ()
}
}
"added=" +
added.to_string() +
", changed=" +
changed.to_string() +
", removed=" +
removed.to_string()
}
}
///|
pub fn ReleasePlan::provider_fingerprint(self : ReleasePlan) -> String {
self.next.fingerprint()
}
///|
pub fn ReleasePlan::rollback_fingerprint(self : ReleasePlan) -> String? {
self.previous.map(provider => provider.fingerprint())
}
///|
pub fn ReleasePlan::reviewer_required(self : ReleasePlan) -> Bool {
self.environment().requires_approval() ||
self.risk.at_least(High) ||
self.replay_required()
}
///|
pub fn ReleasePlan::operator_notes(self : ReleasePlan) -> Array[String] {
let notes = []
notes.push("stage: " + self.stage())
notes.push("provider: " + self.provider_fingerprint())
notes.push("changes: " + self.change_summary())
if self.reviewer_required() {
notes.push("operator review is required before activation")
}
if self.rollback_fingerprint() is Some(fingerprint) {
notes.push("rollback provider: " + fingerprint)
}
notes
}