///|
pub(all) enum CutoverComparisonKind {
GoldenFixtureRun
LiveSnapshotRehearsal
LegacyReadOnlyRollback
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum CutoverComparisonStatus {
ComparisonPass
ComparisonNeedsReview
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverComparisonCase {
id : String
kind : CutoverComparisonKind
status : CutoverComparisonStatus
legacy_ref : String
moonfish_ref : String
evidence_path : String
message : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverComparisonInput {
parity_report : @parity.ParityReport
golden_workspace : @workspace.AnalysisWorkspace
live_bundle : @workflow.AnalysisRunBundle
legacy_read_only_ref : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverComparisonReport {
ready : Bool
pass_count : Int
review_count : Int
golden_case_count : Int
live_case_count : Int
cases : Array[CutoverComparisonCase]
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn cutover_comparison_input(
parity_report : @parity.ParityReport,
golden_workspace : @workspace.AnalysisWorkspace,
live_bundle : @workflow.AnalysisRunBundle,
legacy_read_only_ref? : String = "../paa",
) -> CutoverComparisonInput {
{ parity_report, golden_workspace, live_bundle, legacy_read_only_ref }
}
///|
fn comparison_case(
id : String,
kind : CutoverComparisonKind,
status : CutoverComparisonStatus,
legacy_ref : String,
moonfish_ref : String,
evidence_path : String,
message : String,
) -> CutoverComparisonCase {
{ id, kind, status, legacy_ref, moonfish_ref, evidence_path, message }
}
///|
fn golden_fixture_case(input : CutoverComparisonInput) -> CutoverComparisonCase {
let parity_ready = input.parity_report.review_count == 0 &&
input.parity_report.untraced_case_count == 0 &&
input.parity_report.fixture_case_count == input.parity_report.cases.length()
let workspace_ready = input.golden_workspace.bundle.status is Completed &&
input.golden_workspace.job_state.outcome is Completed &&
input.golden_workspace.book_artifact_count > 0
let status = if parity_ready && workspace_ready {
ComparisonPass
} else {
ComparisonNeedsReview
}
comparison_case(
"golden-fixture-dual-run",
GoldenFixtureRun,
status,
input.parity_report.source_fixture_root,
"records/parity/golden-fixture-report.json",
"parity-report",
"golden PA Agent fixtures are compared to completed Moonfish workspace evidence",
)
}
///|
fn live_snapshot_case(input : CutoverComparisonInput) -> CutoverComparisonCase {
let request = input.live_bundle.request.market
let external_source = request.data_source != Fixture
let completed = input.live_bundle.status is Completed &&
input.live_bundle.market_fetch.status is SnapshotReady &&
input.live_bundle.chart_frame is Some(_) &&
input.live_bundle.write_plan is Some(_)
let status = if external_source && completed {
ComparisonPass
} else {
ComparisonNeedsReview
}
comparison_case(
"live-snapshot-rehearsal",
LiveSnapshotRehearsal,
status,
"live/\{request.symbol}/\{request.timeframe}",
"records/comparisons/live-snapshot-rehearsal.json",
input.live_bundle.market_fetch.plan.raw_path,
"an external closed-bar snapshot can complete the Moonfish workflow without provider logic in Moondesk",
)
}
///|
fn rollback_case(input : CutoverComparisonInput) -> CutoverComparisonCase {
let status = if input.legacy_read_only_ref != "" &&
input.live_bundle.status is Completed &&
input.golden_workspace.replay_plan.can_replay {
ComparisonPass
} else {
ComparisonNeedsReview
}
comparison_case(
"legacy-read-only-rollback",
LegacyReadOnlyRollback,
status,
input.legacy_read_only_ref,
"records/evaluations/rollback-window.json",
"cutover-plan",
"PA Agent remains read-only during the rollback window while Moonfish evidence stays replayable",
)
}
///|
fn count_status(
cases : Array[CutoverComparisonCase],
status : CutoverComparisonStatus,
) -> Int {
cases.fold(init=0, fn(count, item) {
if item.status == status {
count + 1
} else {
count
}
})
}
///|
fn count_kind(
cases : Array[CutoverComparisonCase],
kind : CutoverComparisonKind,
) -> Int {
cases.fold(init=0, fn(count, item) {
if item.kind == kind {
count + 1
} else {
count
}
})
}
///|
pub fn prepare_cutover_comparison_report(
input : CutoverComparisonInput,
) -> CutoverComparisonReport {
let cases = [
golden_fixture_case(input),
live_snapshot_case(input),
rollback_case(input),
]
let review_count = count_status(cases, ComparisonNeedsReview)
let pass_count = count_status(cases, ComparisonPass)
let ready = review_count == 0
{
ready,
pass_count,
review_count,
golden_case_count: count_kind(cases, GoldenFixtureRun),
live_case_count: count_kind(cases, LiveSnapshotRehearsal),
cases,
summary: if ready {
"cutover comparison passed \{pass_count}/\{cases.length()} evidence checks"
} else {
"cutover comparison has \{review_count} evidence check(s) needing review"
},
}
}