///|
/// Exact multiset comparison of one caller-assured logical artifact.
pub struct ReportDelta {
baseline : Report
current : Report
introduced : Array[Diagnostic]
resolved : Array[Diagnostic]
retained : Int32
}
///|
fn report_snapshot(report : Report) -> Report {
{
source: report.source,
statements: report.statements,
complete: report.complete,
diagnostics: report.diagnostics.copy(),
}
}
///|
fn diagnostic_key(report : Report, diagnostic : Diagnostic) -> String {
let span = diagnostic.span
// Ordered JSON tuples avoid delimiter collisions; exact source is retained.
Json([
Json(diagnostic.code),
Json(diagnostic.severity),
Json(report.source[span.start:span.end].to_owned()),
Json(diagnostic.message),
Json(diagnostic.help),
]).stringify()
}
///|
/// Compare complete reports from the same artifact, rule version and caller
/// context. Identity is exact code/severity/message/help plus source-span text;
/// only position shifts are ignored. Duplicate multiplicity is preserved.
/// Incomplete or malformed input returns Err, never a passing partial delta.
///
/// ```mbt check
/// test {
/// let before = @migration_lens.review_sql(
/// "DROP INDEX a;",
/// execution=SeparateStatements,
/// )
/// let after = @migration_lens.review_sql(
/// "-- shifted\nDROP INDEX a;",
/// execution=SeparateStatements,
/// )
/// let delta = @migration_lens.compare_reports(before, after).unwrap()
/// assert_eq(delta.retained, 1)
/// assert_eq(delta.exit_code(), 0)
/// }
/// ```
pub fn compare_reports(
baseline : Report,
current : Report,
) -> Result[ReportDelta, String] {
for (report, label) in [(baseline, "Baseline"), (current, "Current")] {
match validate_report(report, label) {
Ok(_) => ()
Err(e) => return Err(e)
}
if !report.complete {
return Err(
"\{label} analysis is incomplete; resolve coverage before applying a baseline",
)
}
}
let baseline = report_snapshot(baseline)
let current = report_snapshot(current)
let remaining_before : Map[String, Int32] = Map([])
let remaining_after : Map[String, Int32] = Map([])
for d in baseline.diagnostics {
let key = diagnostic_key(baseline, d)
remaining_before[key] = remaining_before.get(key).unwrap_or(0) + 1
}
for d in current.diagnostics {
let key = diagnostic_key(current, d)
remaining_after[key] = remaining_after.get(key).unwrap_or(0) + 1
}
let introduced = []
for d in current.diagnostics {
let key = diagnostic_key(current, d)
let count = remaining_before.get(key).unwrap_or(0)
if count == 0 {
introduced.push(d)
} else {
remaining_before[key] = count - 1
}
}
let resolved = []
for d in baseline.diagnostics {
let key = diagnostic_key(baseline, d)
let count = remaining_after.get(key).unwrap_or(0)
if count == 0 {
resolved.push(d)
} else {
remaining_after[key] = count - 1
}
}
Ok({
baseline,
current,
introduced,
resolved,
retained: current.diagnostics.length() - introduced.length(),
})
}
///|
/// Baseline gate: 1 for introduced findings, otherwise 0. Retained errors remain
/// visible in current; this result is not an absolute migration safety check.
pub fn ReportDelta::exit_code(self : ReportDelta) -> Int32 {
if self.introduced.is_empty() {
0
} else {
1
}
}
///|
pub fn ReportDelta::to_json(self : ReportDelta) -> Json {
{
"format_version": 1,
"baseline": self.baseline.to_json(),
"current": self.current.to_json(),
"introduced": Json(self.introduced),
"resolved": Json(self.resolved),
"retained": self.retained,
"exit_code": self.exit_code(),
}
}
///|
pub fn ReportDelta::to_text(self : ReportDelta) -> String {
let added : Report = {
source: self.current.source,
statements: self.current.statements,
complete: true,
diagnostics: self.introduced,
}
let removed : Report = {
source: self.baseline.source,
statements: self.baseline.statements,
complete: true,
diagnostics: self.resolved,
}
"MigrationLens delta: \{self.introduced.length()} introduced; \{self.resolved.length()} resolved; \{self.retained} retained; exit=\{self.exit_code()}\n" +
"Introduced (current source):\n" +
added.to_text() +
"Resolved (baseline source):\n" +
removed.to_text()
}