///|
pub(all) enum CutoverStepKind {
VerifyReadiness
ImportLegacyData
FreezeLegacyApp
SwitchOperatorSurface
MonitorRollbackWindow
ArchiveLegacyNotes
BlockedByReadiness
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum CutoverStepStatus {
Planned
Blocked
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverStep {
index : Int
kind : CutoverStepKind
status : CutoverStepStatus
path : String
message : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverPlanInput {
readiness : @evaluation.CutoverReadinessReport
import_plan : @migration.LegacyImportPlan
rollback_days : Int
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverPlan {
ready_to_cutover : Bool
rollback_days : Int
imported_data_manifest_path : String
checklist : Array[CutoverStep]
rollback_plan : Array[CutoverStep]
decommission_note : String
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn cutover_plan_input(
readiness : @evaluation.CutoverReadinessReport,
import_plan : @migration.LegacyImportPlan,
rollback_days? : Int = 14,
) -> CutoverPlanInput {
{ readiness, import_plan, rollback_days }
}
///|
fn normalized_rollback_days(days : Int) -> Int {
if days < 1 {
1
} else {
days
}
}
///|
fn step(
index : Int,
kind : CutoverStepKind,
status : CutoverStepStatus,
path : String,
message : String,
) -> CutoverStep {
{ index, kind, status, path, message }
}
///|
fn readiness_blockers(
readiness : @evaluation.CutoverReadinessReport,
) -> Array[CutoverStep] {
let steps : Array[CutoverStep] = []
let mut index = 0
for finding in readiness.findings {
if finding.status is NeedsWork {
steps.push(
step(index, BlockedByReadiness, Blocked, finding.path, finding.message),
)
index += 1
}
}
steps
}
///|
fn ready_checklist(
import_plan : @migration.LegacyImportPlan,
rollback_days : Int,
) -> Array[CutoverStep] {
[
step(
0,
VerifyReadiness,
Planned,
"records/evaluations/cutover-readiness.json",
"store final readiness report and operator approval",
),
step(
1,
ImportLegacyData,
Planned,
"records/imports/\{import_plan.batch_id}.json",
"write \{import_plan.ready_count} imported legacy artifact(s) into MoonBook targets",
),
step(
2,
FreezeLegacyApp,
Planned,
"../paa",
"freeze PA Agent feature development except critical fixes",
),
step(
3,
SwitchOperatorSurface,
Planned,
"apps/moondesk-price-action",
"switch daily workflow to the MoonSuite price-action app-tool",
),
step(
4,
MonitorRollbackWindow,
Planned,
"records/evaluations/rollback-window.json",
"keep PA Agent read-only for \{rollback_days} day(s)",
),
step(
5,
ArchiveLegacyNotes,
Planned,
"docs/pa-agent-migration-plan.md",
"archive migration notes, known differences, and decommission status",
),
]
}
///|
fn rollback_plan(rollback_days : Int) -> Array[CutoverStep] {
[
step(
0,
MonitorRollbackWindow,
Planned,
"../paa",
"during the \{rollback_days}-day window, keep PA Agent read-only but runnable for audit comparison",
),
step(
1,
SwitchOperatorSurface,
Planned,
"apps/moondesk-price-action",
"if a blocking issue appears, switch operators back to the read-only PA Agent workflow and retain MoonBook evidence",
),
step(
2,
ArchiveLegacyNotes,
Planned,
"records/evaluations/rollback-report.json",
"record rollback reason, affected runs, and required Moonfish fixes",
),
]
}
///|
pub fn prepare_cutover_plan(input : CutoverPlanInput) -> CutoverPlan {
let rollback_days = normalized_rollback_days(input.rollback_days)
let ready_to_cutover = input.readiness.ready
let imported_data_manifest_path = "records/imports/\{input.import_plan.batch_id}.json"
let checklist = if ready_to_cutover {
ready_checklist(input.import_plan, rollback_days)
} else {
readiness_blockers(input.readiness)
}
let decommission_note = if ready_to_cutover {
"Moonfish is ready to become the primary workflow after import execution and the \{rollback_days}-day read-only PA Agent rollback window."
} else {
"Moonfish is not ready for cutover; resolve readiness blockers before freezing PA Agent."
}
{
ready_to_cutover,
rollback_days,
imported_data_manifest_path,
checklist,
rollback_plan: rollback_plan(rollback_days),
decommission_note,
summary: if ready_to_cutover {
"cutover plan ready with \{checklist.length()} step(s) and \{input.import_plan.ready_count} import item(s)"
} else {
"cutover blocked by \{checklist.length()} readiness issue(s)"
},
}
}