///|
/// Classification of a diagnostic group relative to a historical baseline.
pub enum BaselineStatus {
New
Existing
Worsened
Resolved
} derive(Eq, Debug)
///|
pub extend BaselineStatus with Eq::{equal, not_equal}
///|
pub extend BaselineStatus with @debug.Debug::{to_repr}
///|
/// An immutable group in a baseline document, identified by rule code and the
/// stable path anchor. members is the full member list with per-member counts.
pub struct BaselineGroup {
code : String
anchor : String
members : Array[SourceMember]
source_total : Int
} derive(Eq, Debug)
///|
pub extend BaselineGroup with Eq::{equal, not_equal}
///|
pub extend BaselineGroup with @debug.Debug::{to_repr}
///|
/// The baseline document: created only from a complete schema 2 audit report,
/// binding the profile, rule version and effective scan scope to its groups.
pub struct Baseline {
format : String
schema_version : Int
profile : String
rules_version : String
scope : Array[String]
groups : Array[BaselineGroup]
} derive(Eq, Debug)
///|
pub extend Baseline with Eq::{equal, not_equal}
///|
pub extend Baseline with @debug.Debug::{to_repr}
///|
/// One classified group after comparing a report against a baseline. members and
/// source_total describe the current report state; a resolved group is empty.
pub struct BaselineChange {
code : String
anchor : String
status : BaselineStatus
members : Array[SourceMember]
source_total : Int
} derive(Eq, Debug)
///|
pub extend BaselineChange with Eq::{equal, not_equal}
///|
pub extend BaselineChange with @debug.Debug::{to_repr}
///|
pub struct BaselineDiff {
changes : Array[BaselineChange]
} derive(Eq, Debug)
///|
pub extend BaselineDiff with Eq::{equal, not_equal}
///|
pub extend BaselineDiff with @debug.Debug::{to_repr}
///|
fn baseline_status_name(status : BaselineStatus) -> String {
match status {
New => "new"
Existing => "existing"
Worsened => "worsened"
Resolved => "resolved"
}
}
///|
fn baseline_status_rank(status : BaselineStatus) -> Int {
match status {
New => 0
Worsened => 1
Existing => 2
Resolved => 3
}
}
///|
fn baseline_group_order(a : BaselineGroup, b : BaselineGroup) -> Int {
let code = ordinal_compare(a.code, b.code)
if code != 0 {
return code
}
ordinal_compare(a.anchor, b.anchor)
}
///|
fn baseline_change_order(a : BaselineChange, b : BaselineChange) -> Int {
let rank_a = baseline_status_rank(a.status)
let rank_b = baseline_status_rank(b.status)
let rank = if rank_a == rank_b { 0 } else if rank_a < rank_b { -1 } else { 1 }
if rank != 0 {
return rank
}
let code = ordinal_compare(a.code, b.code)
if code != 0 {
return code
}
ordinal_compare(a.anchor, b.anchor)
}
///|
/// Return the absolute rule-version string for the current catalog. A baseline
/// records this value so a later report from different rules can be rejected.
pub fn rules_version() -> String {
"1"
}
///|
/// Build a baseline from a complete schema 2 audit report. Incomplete reports
/// and non-schema-2 documents are rejected with structured input errors.
pub fn build_baseline(report : DetailedReport) -> Result[Baseline, InputError] {
if report.schema_version != 2 {
return Err({
code: "INPUT_SCHEMA",
message: "A baseline requires a schema 2 report.",
})
}
if report.complete == false || report.scan_issues.is_empty() == false {
return Err({
code: "INPUT_INCOMPLETE",
message: "A baseline requires a complete report without scan issues.",
})
}
let groups = report.diagnostics.map(d => {
let members = d.members.copy()
members.sort_by(source_member_order)
{ code: d.code, anchor: d.anchor, members, source_total: d.source_total, }
})
groups.sort_by(baseline_group_order)
Ok({
format: "moonportcheck-baseline",
schema_version: 1,
profile: report.profile,
rules_version: rules_version(),
scope: report.scope.copy(),
groups,
})
}
///|
fn member_key(item : SourceMember) -> String {
item.path + "\u0000" + source_kind_name(item.kind)
}
///|
fn member_map(members : Array[SourceMember]) -> Map[String, Int] {
let map : Map[String, Int] = Map([])
for item in members {
let key = member_key(item)
let previous = match map.get(key) {
None => 0
Some(count) => count
}
map[key] = previous + item.count
}
map
}
///|
/// A matched group worsens when a member count rises or a brand-new member
/// path/kind appears. Losing members alone keeps the group existing.
fn group_worsened(before : Map[String, Int], after : Map[String, Int]) -> Bool {
let keys = after.keys().collect()
keys.sort_by(ordinal_compare)
for key in keys {
let previous = match before.get(key) {
None => 0
Some(count) => count
}
if after[key] > previous {
return true
}
}
false
}
///|
/// Classify every diagnostic group of a report against a historical baseline.
/// Groups are associated by rule code plus stable path anchor; members and
/// their counts are compared as multisets. Returns a stable diff document.
pub fn diff_with_baseline(
baseline : Baseline,
report : DetailedReport,
) -> BaselineDiff {
let report_by_key : Map[String, DetailedDiagnostic] = Map([])
for diagnostic in report.diagnostics {
report_by_key[diagnostic.code + "\u0000" + diagnostic.anchor] = diagnostic
}
let baseline_by_key : Map[String, BaselineGroup] = Map([])
for group in baseline.groups {
baseline_by_key[group.code + "\u0000" + group.anchor] = group
}
let changes : Array[BaselineChange] = []
for group in baseline.groups {
let key = group.code + "\u0000" + group.anchor
let status : BaselineStatus = match report_by_key.get(key) {
None => Resolved
Some(current) => {
let before = member_map(group.members)
let after = member_map(current.members)
if group_worsened(before, after) {
Worsened
} else {
Existing
}
}
}
let (members, source_total) = match report_by_key.get(key) {
None => {
let empty : Array[SourceMember] = []
(empty, 0)
}
Some(current) => {
let sorted = current.members.copy()
sorted.sort_by(source_member_order)
(sorted, current.source_total)
}
}
changes.push({
code: group.code,
anchor: group.anchor,
status,
members,
source_total,
})
}
for diagnostic in report.diagnostics {
if baseline_by_key.contains(diagnostic.code + "\u0000" + diagnostic.anchor) {
continue
}
let sorted = diagnostic.members.copy()
sorted.sort_by(source_member_order)
changes.push({
code: diagnostic.code,
anchor: diagnostic.anchor,
status: New,
members: sorted,
source_total: diagnostic.source_total,
})
}
changes.sort_by(baseline_change_order)
{ changes, }
}
///|
/// Stable serialization of a baseline document with fixed field order.
pub fn render_baseline_json(baseline : Baseline) -> String {
"{\"format\":" +
quote(baseline.format) +
",\"schema_version\":\{baseline.schema_version},\"profile\":" +
quote(baseline.profile) +
",\"rules_version\":" +
quote(baseline.rules_version) +
",\"scope\":" +
json_strings(baseline.scope) +
",\"groups\":[" +
baseline.groups
.map(group => {
"{\"code\":" +
quote(group.code) +
",\"anchor\":" +
quote(group.anchor) +
",\"members\":" +
render_source_members(group.members) +
",\"source_total\":\{group.source_total}}"
})
.join(",") +
"]}"
}
///|
/// Stable serialization of a classification document with fixed field order.
pub fn render_baseline_diff_json(diff : BaselineDiff) -> String {
"{\"format\":\"moonportcheck-baseline-diff\",\"schema_version\":1,\"changes\":[" +
diff.changes
.map(change => {
"{\"code\":" +
quote(change.code) +
",\"anchor\":" +
quote(change.anchor) +
",\"status\":" +
quote(baseline_status_name(change.status)) +
",\"members\":" +
render_source_members(change.members) +
",\"source_total\":\{change.source_total}}"
})
.join(",") +
"]}"
}
///|
/// Stable human-readable classification for text output.
pub fn render_baseline_diff_text(diff : BaselineDiff) -> String {
if diff.changes.is_empty() {
return "No baseline changes.\n"
}
let lines = diff.changes.map(change => {
"[" +
baseline_status_name(change.status) +
"] " +
change.code +
"@" +
change.anchor +
" (current_source_total=\{change.source_total})"
})
lines.join("\n") + "\n"
}
///|
/// Parse a baseline document produced by render_baseline_json. Field names are
/// strict so a baseline cannot silently drop a newly added future field.
pub fn parse_baseline(text : String) -> Result[Baseline, InputError] {
let value = @json.parse(text) catch {
_ =>
return Err({
code: "INPUT_JSON",
message: "Invalid JSON in the baseline.",
})
}
guard value is Object(fields) else {
return Err({
code: "INPUT_SCHEMA",
message: "The baseline must be a JSON object.",
})
}
match fields.get("format") {
Some(String("moonportcheck-baseline")) => ()
_ =>
return Err({
code: "INPUT_SCHEMA",
message: "Unsupported baseline format.",
})
}
match fields.get("schema_version") {
Some(Number(n, ..)) if n == 1.0 => ()
_ =>
return Err({
code: "INPUT_SCHEMA",
message: "Unsupported baseline version.",
})
}
guard fields.get("profile") is Some(String(profile)) else {
return Err({
code: "INPUT_SCHEMA",
message: "The baseline requires a profile.",
})
}
guard fields.get("rules_version") is Some(String(rules_version)) else {
return Err({
code: "INPUT_SCHEMA",
message: "The baseline requires a rules_version.",
})
}
let scope = match fields.get("scope") {
Some(Array(items)) =>
match collect_snapshot_strings(items, "scope") {
Err(error) => return Err(error)
Ok(values) => values
}
_ =>
return Err({
code: "INPUT_SCHEMA",
message: "The baseline requires a scope array.",
})
}
let normalized_scope = match parse_scope(scope) {
Err(error) => return Err(error)
Ok(parsed) => effective_scope_patterns(parsed)
}
let groups = match fields.get("groups") {
Some(Array(items)) => {
let list : Array[BaselineGroup] = []
for index in 0.. return Err(error)
Ok(_) => ()
}
guard group_fields.get("code") is Some(String(code)) else {
return Err({
code: "INPUT_SCHEMA",
message: "A baseline group requires a code.",
})
}
guard group_fields.get("anchor") is Some(String(anchor)) else {
return Err({
code: "INPUT_SCHEMA",
message: "A baseline group requires an anchor.",
})
}
let members = match group_fields.get("members") {
Some(Array(items)) =>
match parse_source_members(items) {
Err(error) => return Err(error)
Ok(values) => values
}
_ =>
return Err({
code: "INPUT_SCHEMA",
message: "A baseline group requires a members array.",
})
}
let source_total = match parse_int_field(group_fields, "source_total") {
Err(error) => return Err(error)
Ok(value) => value
}
list.push({ code, anchor, members, source_total, })
}
list
}
_ =>
return Err({
code: "INPUT_SCHEMA",
message: "The baseline requires a groups array.",
})
}
for name in fields.keys() {
match name {
"format"
| "schema_version"
| "profile"
| "rules_version"
| "scope"
| "groups" => ()
_ =>
return Err({
code: "INPUT_SCHEMA",
message: "Unknown baseline field: " + name,
})
}
}
Ok({
format: "moonportcheck-baseline",
schema_version: 1,
profile,
rules_version,
scope: normalized_scope,
groups,
})
}