///|
pub(all) enum ParityGroup {
SchemaValidation
DataFrameAndIndicator
StrategyRouting
TwoStageRoutine
PersistenceAndRedaction
OperatorSurface
SafetyInvariant
ImportTraceability
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum ParityExpectation {
ExactMatch
SemanticEquivalent
IntentionalDifference
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum ParityStatus {
ParityPass
ParityNeedsReview
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum ParityEvidenceKind {
LegacyFixture
MoonfishEvidence
KnownDifferenceNote
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ParityEvidenceRef {
kind : ParityEvidenceKind
path : String
role : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ParityCase {
id : String
group : ParityGroup
expectation : ParityExpectation
status : ParityStatus
source_ref : String
moonfish_ref : String
evidence_refs : Array[ParityEvidenceRef]
assertion : String
note : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ParityReportInput {
workspace : @workspace.AnalysisWorkspace
import_plan : @migration.LegacyImportPlan
source_fixture_root : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct ParityReport {
source_fixture_root : String
pass_count : Int
review_count : Int
exact_count : Int
semantic_count : Int
intentional_difference_count : Int
fixture_case_count : Int
untraced_case_count : Int
cases : Array[ParityCase]
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn parity_report_input(
workspace : @workspace.AnalysisWorkspace,
import_plan : @migration.LegacyImportPlan,
source_fixture_root? : String = "../paa/tests",
) -> ParityReportInput {
{ workspace, import_plan, source_fixture_root }
}
///|
fn parity_case(
id : String,
group : ParityGroup,
expectation : ParityExpectation,
status : ParityStatus,
source_ref : String,
moonfish_ref : String,
evidence_refs : Array[ParityEvidenceRef],
assertion : String,
note : String,
) -> ParityCase {
{
id,
group,
expectation,
status,
source_ref,
moonfish_ref,
evidence_refs,
assertion,
note,
}
}
///|
fn evidence_ref(
kind : ParityEvidenceKind,
path : String,
role : String,
) -> ParityEvidenceRef {
{ kind, path, role }
}
///|
fn legacy_fixture(
root : String,
path : String,
role : String,
) -> ParityEvidenceRef {
evidence_ref(LegacyFixture, "\{root}/\{path}", role)
}
///|
fn moonfish_evidence(path : String, role : String) -> ParityEvidenceRef {
evidence_ref(MoonfishEvidence, path, role)
}
///|
fn known_difference(path : String, role : String) -> ParityEvidenceRef {
evidence_ref(KnownDifferenceNote, path, role)
}
///|
fn schema_validation_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let status = if workspace.retry_decision.issue_class is NoIssue {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"schema-validation",
SchemaValidation,
ExactMatch,
status,
"\{root}/unit/test_json_validator.py",
"schemas/stage1-diagnosis.schema.json",
[
legacy_fixture(
root, "unit/test_json_validator.py", "strict stage schema categories",
),
legacy_fixture(
root, "property/test_json_validator_categories.py", "validator class parity",
),
moonfish_evidence("validation/report.mbt", "stage validation report"),
moonfish_evidence(
"orchestration/retry_policy.mbt", "retry classification",
),
],
"invalid or incomplete stage JSON maps to the same retry/no-retry boundary",
"stage validation reports feed the retry policy with no retry needed for the fixture",
)
}
///|
fn data_feature_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let status = if workspace.chart_candle_count > 0 {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"data-frame-indicators",
DataFrameAndIndicator,
ExactMatch,
status,
"\{root}/property/test_indicators_incremental.py",
"tools/market_features.mbt",
[
legacy_fixture(root, "fixtures/kline_bars.py", "closed-bar fixture data"),
legacy_fixture(
root, "property/test_indicators_incremental.py", "incremental indicator parity",
),
legacy_fixture(
root, "property/test_snapshot_bijection.py", "snapshot round trip",
),
moonfish_evidence("data/features.mbt", "deterministic feature frame"),
moonfish_evidence("chart/frame.mbt", "closed-candle chart projection"),
],
"closed fixture bars produce stable features, chart candles, and snapshot round trips",
"fixture market data produces deterministic candles and feature frames",
)
}
///|
fn routing_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let strategy_count = match workspace.bundle.projection {
Some(projection) =>
match projection.result.record.stage1 {
Some(stage1) => stage1.strategy_files_needed.length()
None => 0
}
None => 0
}
let status = if strategy_count > 0 { ParityPass } else { ParityNeedsReview }
parity_case(
"strategy-routing",
StrategyRouting,
ExactMatch,
status,
"\{root}/unit/test_pattern_routing.py",
"tools/routing.mbt",
[
legacy_fixture(
root, "unit/test_pattern_routing.py", "pattern route fixture",
),
legacy_fixture(
root, "property/test_router_determinism.py", "routing determinism",
),
moonfish_evidence("routing/strategy.mbt", "strategy-file routing output"),
moonfish_evidence("prompt/assembly.mbt", "prompt strategy section"),
],
"the same diagnosis patterns select deterministic strategy notes",
"diagnosis routing produces inspectable strategy-note paths",
)
}
///|
fn routine_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let status = if workspace.job_state.outcome is Completed &&
workspace.job_state.event_count > 0 {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"two-stage-routine",
TwoStageRoutine,
SemanticEquivalent,
status,
"\{root}/integration/test_two_stage_happy_path.py",
"workspace/run_workspace.mbt",
[
legacy_fixture(
root, "integration/test_two_stage_happy_path.py", "happy-path orchestrator fixture",
),
legacy_fixture(
root, "integration/test_two_stage_stage1_missing_field.py", "stage retry fixture",
),
legacy_fixture(
root, "integration/test_two_stage_user_cancel.py", "operator cancel fixture",
),
moonfish_evidence("routine/two_stage.mbt", "MoonClaw routine result"),
moonfish_evidence(
"workspace/run_workspace.mbt", "durable workspace projection",
),
],
"MoonClaw phases preserve the observable PA two-stage lifecycle without a Python orchestrator",
"MoonClaw job/workspace projection replaces the Python orchestrator while preserving phase evidence",
)
}
///|
fn persistence_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let status = if workspace.book_artifact_count > 0 &&
workspace.safety_report.ok {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"persistence-redaction",
PersistenceAndRedaction,
SemanticEquivalent,
status,
"\{root}/unit/test_analysis_history.py",
"book/write_plan.mbt",
[
legacy_fixture(
root, "unit/test_analysis_history.py", "legacy history record",
),
legacy_fixture(
root, "property/test_record_round_trip.py", "record round trip",
),
legacy_fixture(root, "property/test_mask_secret.py", "secret masking"),
moonfish_evidence("book/write_plan.mbt", "MoonBook write plan"),
moonfish_evidence("export/manifest.mbt", "redacted public export"),
],
"analysis records remain round-trippable while public/debug exports redact sensitive evidence",
"MoonBook write plans preserve raw evidence while public export redaction is checked separately",
)
}
///|
fn operator_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let status = if workspace.run_view.actions.contains(StartFollowUp) &&
workspace.run_view.actions.contains(ReplayRun) {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"operator-surface",
OperatorSurface,
SemanticEquivalent,
status,
"\{root}/e2e/test_smoke_happy_path.py",
"app/run_view.mbt",
[
legacy_fixture(
root, "e2e/test_smoke_happy_path.py", "operator happy-path smoke",
),
legacy_fixture(
root, "e2e/test_smoke_switch_mid_flight.py", "operator switch smoke",
),
legacy_fixture(
root, "e2e/test_smoke_free_chat.py", "follow-up/free-chat smoke",
),
moonfish_evidence("app/run_view.mbt", "operator run view"),
moonfish_evidence("app/tool_manifest.mbt", "Moondesk app-tool contract"),
moonfish_evidence("history/index.mbt", "run history projection"),
],
"operator workflow surfaces run, replay, export, follow-up, and debug state through removable app-tool contracts",
"Moondesk app-tool projection exposes the daily operator workflow without PyQt coupling",
)
}
///|
fn safety_case(
workspace : @workspace.AnalysisWorkspace,
root : String,
) -> ParityCase {
let status = if workspace.safety_report.ok {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"safety-invariants",
SafetyInvariant,
IntentionalDifference,
status,
"\{root}/e2e/test_smoke_no_order.py",
"safety/report.mbt",
[
legacy_fixture(root, "e2e/test_smoke_no_order.py", "no-order smoke"),
legacy_fixture(
root, "property/test_stage2_no_order_invariant.py", "no-order invariant",
),
legacy_fixture(
root, "property/test_logs_have_no_plaintext_key.py", "no plaintext key invariant",
),
moonfish_evidence("safety/report.mbt", "suite safety checklist"),
known_difference(
"docs/pa-agent-migration-plan.md#phase-7", "Moonfish intentionally omits broker/order execution surfaces",
),
],
"safety improves on PA by preserving dry-run analysis while removing broker/order authority",
"Moonfish intentionally has no broker/order execution path",
)
}
///|
fn import_case(plan : @migration.LegacyImportPlan, root : String) -> ParityCase {
let status = if plan.ready_count > 0 &&
plan.review_count == 0 &&
plan.skipped_count == 0 {
ParityPass
} else {
ParityNeedsReview
}
parity_case(
"legacy-import-traceability",
ImportTraceability,
SemanticEquivalent,
status,
"\{root}/../records",
"migration/import_plan.mbt",
[
legacy_fixture(root, "../records", "legacy pending analysis records"),
legacy_fixture(root, "../experience", "legacy experience memory"),
legacy_fixture(root, "../trade_records", "legacy trade-log evidence"),
moonfish_evidence("migration/import_plan.mbt", "idempotent import plan"),
moonfish_evidence(
"migration/import_projection.mbt", "MoonBook record projection",
),
],
"legacy records, experience, and trade logs keep stable source paths, target paths, and replay anchors",
"legacy records and experience entries keep source paths, schema version, and trace IDs",
)
}
///|
fn traced_case(item : ParityCase) -> Bool {
item.assertion != "" &&
item.source_ref != "" &&
item.moonfish_ref != "" &&
item.evidence_refs.length() >= 2 &&
item.evidence_refs.any(fn(ref_item) { ref_item.kind is LegacyFixture }) &&
item.evidence_refs.any(fn(ref_item) { ref_item.kind is MoonfishEvidence })
}
///|
fn count_status(cases : Array[ParityCase], status : ParityStatus) -> Int {
cases.fold(init=0, fn(count, item) {
if item.status == status {
count + 1
} else {
count
}
})
}
///|
fn count_expectation(
cases : Array[ParityCase],
expectation : ParityExpectation,
) -> Int {
cases.fold(init=0, fn(count, item) {
if item.expectation == expectation {
count + 1
} else {
count
}
})
}
///|
fn count_traced(cases : Array[ParityCase]) -> Int {
cases.fold(init=0, fn(count, item) {
if traced_case(item) {
count + 1
} else {
count
}
})
}
///|
pub fn prepare_parity_report(input : ParityReportInput) -> ParityReport {
let root = input.source_fixture_root
let cases = [
schema_validation_case(input.workspace, root),
data_feature_case(input.workspace, root),
routing_case(input.workspace, root),
routine_case(input.workspace, root),
persistence_case(input.workspace, root),
operator_case(input.workspace, root),
safety_case(input.workspace, root),
import_case(input.import_plan, root),
]
let pass_count = count_status(cases, ParityPass)
let review_count = count_status(cases, ParityNeedsReview)
let fixture_case_count = count_traced(cases)
let untraced_case_count = cases.length() - fixture_case_count
{
source_fixture_root: root,
pass_count,
review_count,
exact_count: count_expectation(cases, ExactMatch),
semantic_count: count_expectation(cases, SemanticEquivalent),
intentional_difference_count: count_expectation(
cases,
IntentionalDifference,
),
fixture_case_count,
untraced_case_count,
cases,
summary: if review_count == 0 && untraced_case_count == 0 {
"parity report passed \{pass_count}/\{cases.length()} traced fixture groups"
} else if untraced_case_count > 0 {
"parity report has \{untraced_case_count} untraced fixture group(s)"
} else {
"parity report has \{review_count} group(s) needing review"
},
}
}