///|
/// EffectiveValues includes scan additions/removals and exact value-token edits.
/// AllChanges additionally includes assignment-history and source-chain edits.
pub(all) enum ReleasePolicy {
EffectiveValues
AllChanges
} derive(Eq)
///|
pub extend ReleasePolicy with Eq::{equal, not_equal}
///|
pub(all) enum ReleaseDecision {
Pass
ChangesDetected
Indeterminate
} derive(Eq)
///|
pub extend ReleaseDecision with Eq::{equal, not_equal}
///|
pub struct ReleaseCheck {
priv policy : ReleasePolicy
priv impact : ImpactReport
priv affected : Array[String]
priv unresolved : Array[String]
}
///|
/// Errors take precedence over changes. A pass refers only to this policy and
/// these explicit snapshots; it is not a BIDS compliance or completeness claim.
pub fn ReleaseCheck::decision(self : ReleaseCheck) -> ReleaseDecision {
if !self.unresolved.is_empty() {
Indeterminate
} else if !self.affected.is_empty() {
ChangesDetected
} else {
Pass
}
}
///|
pub fn ReleaseCheck::policy(self : ReleaseCheck) -> ReleasePolicy {
self.policy
}
///|
/// Sorted scan paths meeting the selected change policy, even when errors exist.
pub fn ReleaseCheck::affected_paths(self : ReleaseCheck) -> Array[String] {
self.affected.copy()
}
///|
pub fn ReleaseCheck::unresolved_paths(self : ReleaseCheck) -> Array[String] {
self.unresolved.copy()
}
///|
/// Full evidence, including policy-ignored provenance changes and diagnostics.
pub fn ReleaseCheck::impact(self : ReleaseCheck) -> ImpactReport {
self.impact
}
///|
pub fn ImpactReport::release_check(
self : ImpactReport,
policy : ReleasePolicy,
) -> ReleaseCheck {
let affected : Array[String] = []
let unresolved : Array[String] = []
for entry in self.entries {
match entry.kind {
Unresolved => unresolved.push(entry.path)
Added | Removed => affected.push(entry.path)
Changed => {
let relevant = policy == AllChanges ||
entry.changes.iter().any(c => c.kind != ProvenanceChanged)
if relevant {
affected.push(entry.path)
}
}
Unchanged => ()
}
}
{ policy, impact: self, affected, unresolved, }
}
///|
fn release_policy_name(policy : ReleasePolicy) -> String {
match policy {
EffectiveValues => "effective_values"
AllChanges => "all_changes"
}
}
///|
fn release_decision_name(decision : ReleaseDecision) -> String {
match decision {
Pass => "pass"
ChangesDetected => "changes_detected"
Indeterminate => "indeterminate"
}
}
///|
pub fn ReleaseCheck::to_json(self : ReleaseCheck) -> String {
canonical_json(
Json::object({
"policy": Json::string(release_policy_name(self.policy)),
"decision": Json::string(release_decision_name(self.decision())),
"affected_paths": Json::array(self.affected.map(Json::string)),
"unresolved_paths": Json::array(self.unresolved.map(Json::string)),
"impact": self.impact.report_value(),
}),
)
}