///|
pub(all) enum ReadinessGate {
ToolCoverage
WorkspaceExecution
SafetyEvidence
ParityEvidence
CutoverComparisonEvidence
PromptSkillMigration
ReplayEvidence
ImportCoverage
OperatorSurface
ExportRedaction
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum ReadinessStatus {
Ready
NeedsWork
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ReadinessFinding {
gate : ReadinessGate
status : ReadinessStatus
path : String
message : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverEvaluationInput {
workspace : @workspace.AnalysisWorkspace
import_plan : @migration.LegacyImportPlan
parity_report : @parity.ParityReport
comparison_report : @comparison.CutoverComparisonReport
skill_migration_plan : @skills.SkillMigrationPlan
manifest : @pack.PackManifest
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct CutoverReadinessReport {
ready : Bool
pass_count : Int
needs_work_count : Int
findings : Array[ReadinessFinding]
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn cutover_evaluation_input(
workspace : @workspace.AnalysisWorkspace,
import_plan : @migration.LegacyImportPlan,
parity_report : @parity.ParityReport,
comparison_report : @comparison.CutoverComparisonReport,
skill_migration_plan? : @skills.SkillMigrationPlan = @skills.prepare_skill_migration_plan(),
manifest? : @pack.PackManifest = @pack.price_action_manifest(),
) -> CutoverEvaluationInput {
{
workspace,
import_plan,
parity_report,
comparison_report,
skill_migration_plan,
manifest,
}
}
///|
fn finding(
gate : ReadinessGate,
status : ReadinessStatus,
path : String,
message : String,
) -> ReadinessFinding {
{ gate, status, path, message }
}
///|
fn required_tool_names() -> Array[String] {
[
"prepare_analysis_workspace", "prepare_analysis_plan", "prepare_analysis_job",
"prepare_retry_decision", "prepare_legacy_import_plan", "prepare_market_refresh_plan",
"prepare_context_budget_plan", "fetch_kline_snapshot", "compute_indicators",
"compute_structure_levels", "classify_market_structure", "route_strategy_files",
"validate_stage_json", "evaluate_decision_policy", "write_analysis_record", "prepare_book_commit_report",
"queue_analysis_review", "prepare_followup_context", "prepare_followup_session_plan",
"prepare_followup_response", "prepare_replay_plan", "prepare_export_manifest",
"prepare_safety_report", "prepare_redaction_audit", "prepare_cancellation_report",
"prepare_provider_failure_report", "prepare_record_integrity_report", "prepare_moondesk_app_smoke_report",
"render_operator_run_view", "render_run_history", "prepare_cutover_comparison_report",
"prepare_product_launch_manifest", "prepare_product_rehearsal_report", "prepare_release_certificate",
"prepare_rollback_window_report", "prepare_operations_readiness_report", "prepare_skill_migration_plan",
]
}
///|
fn tool_declared(manifest : @pack.PackManifest, name : String) -> Bool {
manifest.tools.any(fn(tool) { tool.name == name })
}
///|
fn missing_required_tool_count(manifest : @pack.PackManifest) -> Int {
required_tool_names().fold(init=0, fn(count, name) {
if tool_declared(manifest, name) {
count
} else {
count + 1
}
})
}
///|
fn tool_coverage_finding(manifest : @pack.PackManifest) -> ReadinessFinding {
let missing = missing_required_tool_count(manifest)
if missing == 0 {
finding(
ToolCoverage,
Ready,
"pack.tools",
"required analysis, import, replay, export, safety, and operator tools are declared",
)
} else {
finding(
ToolCoverage,
NeedsWork,
"pack.tools",
"\{missing} required tool contract(s) are missing",
)
}
}
///|
fn workspace_execution_finding(
workspace : @workspace.AnalysisWorkspace,
) -> ReadinessFinding {
if workspace.bundle.status is Completed &&
workspace.job_state.outcome is Completed &&
workspace.book_artifact_count > 0 &&
workspace.chart_candle_count > 0 {
finding(
WorkspaceExecution,
Ready,
"analysis-workspace",
"fixture workspace completes and produces chart plus MoonBook artifacts",
)
} else {
finding(
WorkspaceExecution,
NeedsWork,
"analysis-workspace",
"workspace does not yet prove a complete analysis path",
)
}
}
///|
fn safety_finding(workspace : @workspace.AnalysisWorkspace) -> ReadinessFinding {
if workspace.safety_report.ok {
finding(
SafetyEvidence,
Ready,
"safety-report",
"safety report passes broker, context budget, export, replay, and ownership checks",
)
} else {
finding(
SafetyEvidence,
NeedsWork,
"safety-report",
"one or more safety checks failed",
)
}
}
///|
fn replay_finding(workspace : @workspace.AnalysisWorkspace) -> ReadinessFinding {
if workspace.replay_plan.can_replay &&
!workspace.replay_plan.live_market_required {
finding(
ReplayEvidence,
Ready,
"replay-plan",
"replay can run from persisted evidence without live market data",
)
} else {
finding(
ReplayEvidence,
NeedsWork,
"replay-plan",
"replay still lacks persisted evidence or requires live market data",
)
}
}
///|
fn parity_finding(report : @parity.ParityReport) -> ReadinessFinding {
if report.review_count == 0 &&
report.untraced_case_count == 0 &&
report.fixture_case_count == report.cases.length() &&
report.pass_count == report.cases.length() {
finding(
ParityEvidence,
Ready,
"parity-report",
"deterministic parity groups pass with traced PA Agent fixtures and Moonfish evidence",
)
} else {
finding(
ParityEvidence,
NeedsWork,
"parity-report",
"one or more parity groups still need review or fixture traceability",
)
}
}
///|
fn import_finding(plan : @migration.LegacyImportPlan) -> ReadinessFinding {
if plan.ready_count > 0 && plan.review_count == 0 && plan.skipped_count == 0 {
finding(
ImportCoverage,
Ready,
"legacy-import-plan",
"legacy PA Agent sources have idempotent MoonBook import targets",
)
} else {
finding(
ImportCoverage,
NeedsWork,
"legacy-import-plan",
"legacy import plan still has review or skipped items",
)
}
}
///|
fn skill_migration_finding(
plan : @skills.SkillMigrationPlan,
) -> ReadinessFinding {
if plan.source_count > 0 &&
plan.mapped_count == plan.source_count &&
plan.review_count == 0 &&
plan.catalog.assets.length() > 0 &&
plan.sources.all(fn(source) { source.target_book_path != "" }) {
finding(
PromptSkillMigration,
Ready,
"skill-migration-plan",
"PA prompt files map to inspectable MoonBook skill, strategy-note, and concept targets",
)
} else {
finding(
PromptSkillMigration,
NeedsWork,
"skill-migration-plan",
"PA prompt migration still has unmapped sources or missing MoonBook targets",
)
}
}
///|
fn comparison_finding(
report : @comparison.CutoverComparisonReport,
) -> ReadinessFinding {
if report.ready && report.golden_case_count > 0 && report.live_case_count > 0 {
finding(
CutoverComparisonEvidence,
Ready,
"cutover-comparison-report",
"golden fixture and live snapshot rehearsal comparisons are ready",
)
} else {
finding(
CutoverComparisonEvidence,
NeedsWork,
"cutover-comparison-report",
"golden fixture or live snapshot comparison evidence still needs review",
)
}
}
///|
fn operator_surface_finding(
workspace : @workspace.AnalysisWorkspace,
) -> ReadinessFinding {
let actions = workspace.run_view.actions
if actions.contains(StartFollowUp) &&
actions.contains(ReplayRun) &&
actions.contains(ExportRun) &&
workspace.history.total_count > 0 {
finding(
OperatorSurface,
Ready,
"operator-run-view",
"operator view exposes follow-up, replay, export, and run history",
)
} else {
finding(
OperatorSurface,
NeedsWork,
"operator-run-view",
"operator surface lacks a cutover workflow action or history row",
)
}
}
///|
fn public_export_finding(
workspace : @workspace.AnalysisWorkspace,
) -> ReadinessFinding {
let redaction_ok = workspace.safety_report.findings.any(fn(item) {
item.kind is PublicExportRedaction && item.status is Pass
})
if redaction_ok {
finding(
ExportRedaction,
Ready,
"export.public-summary",
"public exports omit prompt packets and raw model exchanges",
)
} else {
finding(
ExportRedaction,
NeedsWork,
"export.public-summary",
"public export redaction is not proven",
)
}
}
///|
fn count_status(
items : Array[ReadinessFinding],
status : ReadinessStatus,
) -> Int {
items.fold(init=0, fn(count, item) {
if item.status == status {
count + 1
} else {
count
}
})
}
///|
pub fn prepare_cutover_readiness_report(
input : CutoverEvaluationInput,
) -> CutoverReadinessReport {
let findings = [
tool_coverage_finding(input.manifest),
workspace_execution_finding(input.workspace),
safety_finding(input.workspace),
parity_finding(input.parity_report),
comparison_finding(input.comparison_report),
skill_migration_finding(input.skill_migration_plan),
replay_finding(input.workspace),
import_finding(input.import_plan),
operator_surface_finding(input.workspace),
public_export_finding(input.workspace),
]
let needs_work = count_status(findings, NeedsWork)
let pass_count = count_status(findings, Ready)
let ready = needs_work == 0
{
ready,
pass_count,
needs_work_count: needs_work,
findings,
summary: if ready {
"cutover readiness passed \{pass_count}/\{findings.length()} gates"
} else {
"cutover readiness has \{needs_work} gate(s) needing work"
},
}
}