From 39ab37e1e14b30f630ea0c9c23973d6ff470e740 Mon Sep 17 00:00:00 2001 From: Sonic Shih Date: Tue, 4 Aug 2026 00:40:21 +0800 Subject: [PATCH 1/4] feat(alpha): persist immutable CEX baseline evidence (#600) --- .../alpha-harness/app/src/mission_runner.rs | 172 +++++++++++++++++- 1 file changed, 165 insertions(+), 7 deletions(-) diff --git a/rust_hft/alpha-harness/app/src/mission_runner.rs b/rust_hft/alpha-harness/app/src/mission_runner.rs index 4a10e897..7cb96ed1 100644 --- a/rust_hft/alpha-harness/app/src/mission_runner.rs +++ b/rust_hft/alpha-harness/app/src/mission_runner.rs @@ -5,12 +5,13 @@ use crate::{ data_mission, governance, mission, }; use alpha_domain::{ - canonical_json_hash, CandidateArtifact, CandidateEvaluation, CexFactorBankRevisionV2, - CexFactorEvaluationEvidenceV2, CexFactorRejectionCodeV1, CexFactorScreeningAttemptV2, - CexFactorScreeningVerdictV1, CexGpPolicyV1, CexResearchMissionArtifactV1, EvaluationCostsV1, - FormulaEvaluatorConfig, IterationVerdict, MissionCompletionPolicy, MissionStatus, - ResearchMission, ValidatorMode, + canonical_json_hash, CandidateArtifact, CandidateEvaluation, CexBaselinePolicyV1, + CexFactorBankRevisionV2, CexFactorEvaluationEvidenceV2, CexFactorRejectionCodeV1, + CexFactorScreeningAttemptV2, CexFactorScreeningVerdictV1, CexGpPolicyV1, + CexResearchMissionArtifactV1, EvaluationCostsV1, FormulaEvaluatorConfig, IterationVerdict, + MissionCompletionPolicy, MissionStatus, ResearchMission, ValidatorMode, }; +use alpha_engine::{baselines::evaluate_cex_baselines, evaluation::prepare_dataset}; use alpha_store::{AlphaStore, MissionLineage, RegistryRevision, StoreError}; use anyhow::{bail, Context}; use chrono::Utc; @@ -33,6 +34,9 @@ use std::{ use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter}; const MATERIALIZATION_KIND: &str = "lob_point_in_time_materialization"; +const CEX_BASELINE_RIDGE_REGISTRY_KIND: &str = "cex_baseline_ridge"; +const CEX_BASELINE_CART_REGISTRY_KIND: &str = "cex_baseline_cart"; +const CEX_BASELINE_GATE_REGISTRY_KIND: &str = "cex_baseline_gate"; const MAX_MISSION_BYTES: u64 = 4 * 1024 * 1024; // ponytail: one Mission is capped at 1 GiB; raise this only when staged partitions exceed it. const MAX_FEATURE_BYTES: u64 = 1024 * 1024 * 1024; @@ -166,6 +170,10 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { )?; gp_policy.validate_binding(&control_mission.spec.policies.gp)?; data_mission::write_json_atomic(&results_dir.join("gp-policy.json"), &gp_policy)?; + let baseline_policy = + CexBaselinePolicyV1::controlled_v1(control_mission.spec.policies.baseline.id.clone())?; + baseline_policy.validate_binding(&control_mission.spec.policies.baseline)?; + data_mission::write_json_atomic(&results_dir.join("baseline-policy.json"), &baseline_policy)?; let mission_id = control_mission.semantic_id()?; let validation = ValidationArgs::from_protocol(&control_mission.spec.evaluation_protocol); let engine = EngineChoice::Gp; @@ -299,7 +307,7 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { drop(store); let dataset = DatasetArgs { - dataset_manifest: dataset_manifest_path, + dataset_manifest: dataset_manifest_path.clone(), validation, }; let run_args = RunMissionArgs { @@ -347,6 +355,46 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { created_at: Utc::now(), })?; data_mission::write_json_atomic(&results_dir.join("factor-bank.json"), &factor_bank)?; + let baseline_dataset_manifest = + data_mission::read_registered_research_dataset(&store, &dataset_manifest_path)?; + let baseline_rows = baseline_dataset_manifest.load_rows( + evaluation_protocol.costs.fee_bps, + evaluation_protocol.costs.funding_bps, + evaluation_protocol.costs.latency_bps, + )?; + let baseline_dataset = prepare_dataset(baseline_rows, &evaluation_protocol)?; + let baseline_context = baseline_dataset.engine_context(); + let baseline_target = control_mission + .spec + .hypotheses + .first() + .context("CEX Research Mission has no baseline target")? + .target + .clone(); + if control_mission + .spec + .hypotheses + .iter() + .any(|hypothesis| hypothesis.target != baseline_target) + { + bail!("CEX Research Mission hypotheses do not share one frozen baseline target"); + } + let baseline_run = evaluate_cex_baselines( + &baseline_context, + &factor_bank, + &baseline_policy, + &mission_id, + baseline_target, + &control_mission.spec.policies.evaluation, + ) + .map_err(anyhow::Error::msg)?; + persist_baseline_evidence( + &mut store, + &results_dir, + &control_mission, + &factor_bank, + baseline_run, + )?; let checkpoint = match store.get_checkpoint(&mission_id) { Ok(checkpoint) => Some(checkpoint), Err(StoreError::NotFound) => None, @@ -391,6 +439,69 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { }) } +fn persist_baseline_evidence( + store: &mut AlphaStore, + results_dir: &Path, + control_mission: &CexResearchMissionArtifactV1, + factor_bank: &CexFactorBankRevisionV2, + run: alpha_engine::baselines::CexBaselineRun, +) -> anyhow::Result<()> { + let alpha_engine::baselines::CexBaselineRun { ridge, cart, gate } = run; + let asset_id = control_mission.spec.instrument.symbol.clone(); + if factor_bank.entries.is_empty() { + if ridge.is_some() || cart.is_some() { + bail!("empty Factor Bank baseline evaluation returned model artifacts"); + } + gate.validate_binding(factor_bank, None, None)?; + store.put_registry_revision(&RegistryRevision { + revision_id: gate.gate_id.clone(), + registry_kind: CEX_BASELINE_GATE_REGISTRY_KIND.to_string(), + asset_id, + parent_revision_id: Some(factor_bank.revision_id.clone()), + payload: serde_json::to_value(&gate)?, + created_at: Utc::now(), + })?; + data_mission::write_json_atomic(&results_dir.join("baseline-gate.json"), &gate)?; + return Ok(()); + } + + let ridge = ridge.context("non-empty Factor Bank baseline is missing Ridge artifact")?; + let cart = cart.context("non-empty Factor Bank baseline is missing CART artifact")?; + ridge.validate_binding(control_mission, factor_bank)?; + cart.validate_binding(control_mission, factor_bank)?; + gate.validate_binding(factor_bank, Some(&ridge), Some(&cart))?; + for (artifact_id, registry_kind, payload) in [ + ( + ridge.artifact_id.clone(), + CEX_BASELINE_RIDGE_REGISTRY_KIND, + serde_json::to_value(&ridge)?, + ), + ( + cart.artifact_id.clone(), + CEX_BASELINE_CART_REGISTRY_KIND, + serde_json::to_value(&cart)?, + ), + ( + gate.gate_id.clone(), + CEX_BASELINE_GATE_REGISTRY_KIND, + serde_json::to_value(&gate)?, + ), + ] { + store.put_registry_revision(&RegistryRevision { + revision_id: artifact_id, + registry_kind: registry_kind.to_string(), + asset_id: asset_id.clone(), + parent_revision_id: Some(factor_bank.revision_id.clone()), + payload, + created_at: Utc::now(), + })?; + } + data_mission::write_json_atomic(&results_dir.join("ridge-baseline.json"), &ridge)?; + data_mission::write_json_atomic(&results_dir.join("cart-baseline.json"), &cart)?; + data_mission::write_json_atomic(&results_dir.join("baseline-gate.json"), &gate)?; + Ok(()) +} + fn build_factor_bank( control_mission: &CexResearchMissionArtifactV1, gp_policy: &CexGpPolicyV1, @@ -1299,6 +1410,43 @@ mod tests { std::fs::remove_dir_all(fixture.root).unwrap(); } + #[test] + fn execute_emits_passing_ridge_and_cart_baseline_evidence() { + let mut fixture = fixture("ridge-cart-baselines"); + fixture.mission.spec.feature_fields = vec!["book_imbalance".to_string()]; + rewrite_features(&mut fixture, |row| { + let direction = row.label.signum(); + row.features.insert("book_imbalance".to_string(), direction); + row.label = direction * 0.001; + }); + + execute(fixture.args.clone()).unwrap(); + + let results = fixture.args.work_dir.join("results"); + let ridge: serde_json::Value = + serde_json::from_slice(&std::fs::read(results.join("ridge-baseline.json")).unwrap()) + .unwrap(); + let cart: serde_json::Value = + serde_json::from_slice(&std::fs::read(results.join("cart-baseline.json")).unwrap()) + .unwrap(); + let gate: serde_json::Value = + serde_json::from_slice(&std::fs::read(results.join("baseline-gate.json")).unwrap()) + .unwrap(); + + assert_eq!(ridge["model_kind"], "ridge"); + assert_eq!(cart["model_kind"], "shallow_cart"); + assert_eq!( + ridge["factor_bank_revision_id"], + cart["factor_bank_revision_id"] + ); + assert_eq!(ridge["folds"].as_array().unwrap().len(), 2); + assert_eq!(cart["folds"].as_array().unwrap().len(), 2); + assert_eq!(gate["passed"], true); + assert_eq!(gate["ridge_artifact_id"], ridge["artifact_id"]); + assert_eq!(gate["cart_artifact_id"], cart["artifact_id"]); + std::fs::remove_dir_all(fixture.root).unwrap(); + } + #[test] fn execute_rejects_gp_policy_drift_before_screening() { let mut fixture = fixture("gp-policy-drift"); @@ -1707,6 +1855,7 @@ mod tests { &search.budget, ) .unwrap(); + let baseline_policy = CexBaselinePolicyV1::controlled_v1("baseline-policy-1").unwrap(); let reference = |id: &str, byte: char| CexResearchContentRefV1 { id: id.to_string(), content_sha256: byte.to_string().repeat(64), @@ -1775,7 +1924,10 @@ mod tests { content_sha256: gp_policy.content_hash().unwrap(), }, screening: reference("screening-policy-1", '2'), - baseline: reference("baseline-policy-1", '3'), + baseline: CexResearchContentRefV1 { + id: baseline_policy.policy_id.clone(), + content_sha256: baseline_policy.content_hash().unwrap(), + }, subset_search: CexResearchContentRefV1 { id: "subset-search-policy-1".to_string(), content_sha256: canonical_json_hash(&search).unwrap(), @@ -1880,6 +2032,12 @@ mod tests { if let Ok(gp_policy) = gp_policy { fixture.mission.spec.policies.gp.content_sha256 = gp_policy.content_hash().unwrap(); } + if let Ok(baseline_policy) = + CexBaselinePolicyV1::controlled_v1(fixture.mission.spec.policies.baseline.id.clone()) + { + fixture.mission.spec.policies.baseline.content_sha256 = + baseline_policy.content_hash().unwrap(); + } fixture.mission.spec.policies.subset_search.content_sha256 = canonical_json_hash(&fixture.mission.spec.search).unwrap(); fixture.mission.spec.policies.screening.content_sha256 = canonical_json_hash( From 0d36b4ad27ebeab057bc9ffb4c96a088204bacc5 Mon Sep 17 00:00:00 2001 From: Sonic Shih Date: Tue, 4 Aug 2026 05:33:15 +0800 Subject: [PATCH 2/4] fix(alpha): bind persisted baselines to producer policy (#600) --- .../alpha-harness/app/src/mission_runner.rs | 109 +++++++++++++++--- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/rust_hft/alpha-harness/app/src/mission_runner.rs b/rust_hft/alpha-harness/app/src/mission_runner.rs index 7cb96ed1..019393e6 100644 --- a/rust_hft/alpha-harness/app/src/mission_runner.rs +++ b/rust_hft/alpha-harness/app/src/mission_runner.rs @@ -34,6 +34,7 @@ use std::{ use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter}; const MATERIALIZATION_KIND: &str = "lob_point_in_time_materialization"; +const CEX_BASELINE_POLICY_REGISTRY_KIND: &str = "cex_baseline_policy"; const CEX_BASELINE_RIDGE_REGISTRY_KIND: &str = "cex_baseline_ridge"; const CEX_BASELINE_CART_REGISTRY_KIND: &str = "cex_baseline_cart"; const CEX_BASELINE_GATE_REGISTRY_KIND: &str = "cex_baseline_gate"; @@ -161,6 +162,21 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { serde_json::from_slice(&std::fs::read(&mission_path)?) .context("CEX Research Mission artifact is invalid JSON or schema")?; control_mission.validate()?; + let baseline_target = control_mission + .spec + .hypotheses + .first() + .context("CEX Research Mission has no baseline target")? + .target + .clone(); + if control_mission + .spec + .hypotheses + .iter() + .any(|hypothesis| hypothesis.target != baseline_target) + { + bail!("CEX Research Mission hypotheses do not share one frozen baseline target"); + } mission::validate_live_feature_fields(&control_mission.spec.feature_fields)?; let gp_policy = CexGpPolicyV1::controlled_v1( control_mission.spec.policies.gp.id.clone(), @@ -257,6 +273,15 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { payload: semantic_mission, created_at: now, })?; + let baseline_policy_revision_id = baseline_policy.content_hash()?; + store.put_registry_revision(&RegistryRevision { + revision_id: baseline_policy_revision_id, + registry_kind: CEX_BASELINE_POLICY_REGISTRY_KIND.to_string(), + asset_id: control_mission.spec.instrument.symbol.clone(), + parent_revision_id: Some(mission_id.clone()), + payload: serde_json::to_value(&baseline_policy)?, + created_at: now, + })?; data_mission::write_json_atomic( &results_dir.join("control-plane-mission.json"), &control_mission, @@ -364,21 +389,6 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { )?; let baseline_dataset = prepare_dataset(baseline_rows, &evaluation_protocol)?; let baseline_context = baseline_dataset.engine_context(); - let baseline_target = control_mission - .spec - .hypotheses - .first() - .context("CEX Research Mission has no baseline target")? - .target - .clone(); - if control_mission - .spec - .hypotheses - .iter() - .any(|hypothesis| hypothesis.target != baseline_target) - { - bail!("CEX Research Mission hypotheses do not share one frozen baseline target"); - } let baseline_run = evaluate_cex_baselines( &baseline_context, &factor_bank, @@ -392,6 +402,7 @@ pub fn execute(args: ExecuteMissionArgs) -> anyhow::Result<()> { &mut store, &results_dir, &control_mission, + &baseline_policy, &factor_bank, baseline_run, )?; @@ -443,6 +454,7 @@ fn persist_baseline_evidence( store: &mut AlphaStore, results_dir: &Path, control_mission: &CexResearchMissionArtifactV1, + baseline_policy: &CexBaselinePolicyV1, factor_bank: &CexFactorBankRevisionV2, run: alpha_engine::baselines::CexBaselineRun, ) -> anyhow::Result<()> { @@ -452,7 +464,7 @@ fn persist_baseline_evidence( if ridge.is_some() || cart.is_some() { bail!("empty Factor Bank baseline evaluation returned model artifacts"); } - gate.validate_binding(factor_bank, None, None)?; + gate.validate_binding(control_mission, baseline_policy, factor_bank, None, None)?; store.put_registry_revision(&RegistryRevision { revision_id: gate.gate_id.clone(), registry_kind: CEX_BASELINE_GATE_REGISTRY_KIND.to_string(), @@ -467,9 +479,15 @@ fn persist_baseline_evidence( let ridge = ridge.context("non-empty Factor Bank baseline is missing Ridge artifact")?; let cart = cart.context("non-empty Factor Bank baseline is missing CART artifact")?; - ridge.validate_binding(control_mission, factor_bank)?; - cart.validate_binding(control_mission, factor_bank)?; - gate.validate_binding(factor_bank, Some(&ridge), Some(&cart))?; + ridge.validate_binding(control_mission, baseline_policy, factor_bank)?; + cart.validate_binding(control_mission, baseline_policy, factor_bank)?; + gate.validate_binding( + control_mission, + baseline_policy, + factor_bank, + Some(&ridge), + Some(&cart), + )?; for (artifact_id, registry_kind, payload) in [ ( ridge.artifact_id.clone(), @@ -1432,6 +1450,10 @@ mod tests { let gate: serde_json::Value = serde_json::from_slice(&std::fs::read(results.join("baseline-gate.json")).unwrap()) .unwrap(); + let policy: CexBaselinePolicyV1 = + serde_json::from_slice(&std::fs::read(results.join("baseline-policy.json")).unwrap()) + .unwrap(); + let policy_hash = policy.content_hash().unwrap(); assert_eq!(ridge["model_kind"], "ridge"); assert_eq!(cart["model_kind"], "shallow_cart"); @@ -1444,6 +1466,55 @@ mod tests { assert_eq!(gate["passed"], true); assert_eq!(gate["ridge_artifact_id"], ridge["artifact_id"]); assert_eq!(gate["cart_artifact_id"], cart["artifact_id"]); + assert_eq!(gate["policy_hash"], policy_hash); + let store = AlphaStore::open(results.join("alpha.duckdb")).unwrap(); + let policy_revision = store.get_registry_revision(&policy_hash).unwrap(); + assert_eq!( + policy_revision.registry_kind, + CEX_BASELINE_POLICY_REGISTRY_KIND + ); + assert_eq!(policy_revision.revision_id, policy_hash); + assert_eq!( + policy_revision.asset_id, + fixture.mission.spec.instrument.symbol + ); + assert_eq!( + policy_revision.parent_revision_id.as_deref(), + Some(fixture.mission.semantic_id().unwrap().as_str()) + ); + assert_eq!( + policy_revision.payload, + serde_json::to_value(&policy).unwrap() + ); + std::fs::remove_dir_all(fixture.root).unwrap(); + } + + #[test] + fn execute_rejects_mixed_hypothesis_targets_before_side_effects() { + let mut fixture = fixture("mixed-hypothesis-targets"); + let mut second = fixture.mission.spec.hypotheses[0].clone(); + second.hypothesis_id = "hypothesis-2".to_string(); + second.target.name = "forward_mid_return_other".to_string(); + fixture.mission.spec.hypotheses.push(second); + write_mission(&mut fixture); + + let error = execute(fixture.args.clone()).unwrap_err(); + + assert!(error + .to_string() + .contains("CEX Research Mission hypotheses do not share one frozen baseline target")); + assert!(!fixture.args.work_dir.join("input/features.jsonl").exists()); + assert!(!fixture.args.work_dir.join("results/alpha.duckdb").exists()); + assert!(!fixture + .args + .work_dir + .join("results/gp-policy.json") + .exists()); + assert!(!fixture + .args + .work_dir + .join("results/baseline-policy.json") + .exists()); std::fs::remove_dir_all(fixture.root).unwrap(); } From 07678976eb58b3588e06cb626cf527bba33d681e Mon Sep 17 00:00:00 2001 From: Sonic Shih Date: Tue, 4 Aug 2026 00:40:21 +0800 Subject: [PATCH 3/4] feat(alpha): gate MCTS on immutable CEX baselines (#600) --- rust_hft/alpha-harness/app/src/mission.rs | 255 +++++++++++++++++++--- 1 file changed, 220 insertions(+), 35 deletions(-) diff --git a/rust_hft/alpha-harness/app/src/mission.rs b/rust_hft/alpha-harness/app/src/mission.rs index b3587602..04046edb 100644 --- a/rust_hft/alpha-harness/app/src/mission.rs +++ b/rust_hft/alpha-harness/app/src/mission.rs @@ -3,13 +3,12 @@ use crate::{ data_mission, }; use alpha_domain::{ - canonical_json_hash, CexGpPolicyV1, CexResearchContentRefV1, MissionStatus, ResearchMission, + canonical_json_hash, CexBaselineArtifactV1, CexBaselineGateV1, CexBaselineModelKindV1, + CexFactorBankRevisionV2, CexGpPolicyV1, CexResearchContentRefV1, CexResearchMissionArtifactV1, + MissionStatus, ResearchMission, }; use alpha_engine::{ - engines::{ - CexMctsSearchIdentityV1, GeneticProgrammingEngine, MctsEngine, OfflineRlEngine, - OfflineTrace, - }, + engines::{GeneticProgrammingEngine, OfflineRlEngine, OfflineTrace}, evaluation::prepare_dataset, formula_evaluator::FormulaEvaluator, learning::{close_learning_loop, FailureCritic, LearningConfig}, @@ -24,6 +23,11 @@ pub(crate) const BAYESIAN_WINDOW_SEARCH_LIVE_CAPABILITY_ERROR: &str = "Bayesian window search is research-only and cannot produce live-executable formulas"; pub(crate) const OFFLINE_RL_LIVE_CAPABILITY_ERROR: &str = "Offline RL search is research-only and cannot produce live-executable formulas"; +const CEX_FACTOR_BANK_REGISTRY_KIND: &str = "cex_factor_bank"; +const CEX_BASELINE_RIDGE_REGISTRY_KIND: &str = "cex_baseline_ridge"; +const CEX_BASELINE_CART_REGISTRY_KIND: &str = "cex_baseline_cart"; +const CEX_BASELINE_GATE_REGISTRY_KIND: &str = "cex_baseline_gate"; +const CEX_MCTS_CANDIDATE_SPACE_ID: &str = "live-factor-ast-add-secondary-v1"; #[derive(Debug, Clone, serde::Serialize)] #[serde(rename_all = "snake_case")] @@ -90,6 +94,7 @@ fn execute_mission_inner( )?; let labels = manifest.evaluation_label_spec()?; let protocol = args.dataset.validation.evaluation_protocol(&labels)?; + let evaluation_protocol_hash = protocol.content_hash()?; let dataset = prepare_dataset(rows, &protocol)?; let research_context = dataset.engine_context(); let research_dataset_sha256 = canonical_json_hash(&research_context.rows())?; @@ -105,18 +110,20 @@ fn execute_mission_inner( id: format!("cex-walk-forward-partition-{walk_forward_partition_sha256}"), content_sha256: walk_forward_partition_sha256, }; + if matches!(args.engine, EngineChoice::Mcts) { + validate_mcts_baseline_gate( + &store, + &mission, + &research_dataset, + &walk_forward_partition, + &evaluation_protocol_hash, + )?; + bail!( + "MCTS candidate space {CEX_MCTS_CANDIDATE_SPACE_ID} is not authorized for CEX Factor Bank missions; Factor-Bank subset adapter (#601) is required" + ); + } let evaluator = FormulaEvaluator::for_mission(&mission).map_err(anyhow::Error::msg)?; - let evaluator_config_hash = - canonical_json_hash(&evaluator.config_evidence().map_err(anyhow::Error::msg)?)?; - let evaluation_protocol_hash = protocol.content_hash()?; - let proposal_engine = build_engine( - args, - &dataset, - &mission, - &evaluation_protocol_hash, - &evaluator_config_hash, - governed_gp, - )?; + let proposal_engine = build_engine(args, &dataset, &mission, governed_gp)?; let mut kernel = AutoResearchKernel::new(&mut store, proposal_engine, evaluator); let outcome = kernel.run( &args.mission_id, @@ -142,6 +149,155 @@ fn execute_mission_inner( }) } +fn validate_mcts_baseline_gate( + store: &AlphaStore, + mission: &ResearchMission, + research_dataset: &CexResearchContentRefV1, + walk_forward_partition: &CexResearchContentRefV1, + evaluation_protocol_hash: &str, +) -> anyhow::Result<()> { + let gate_id = mission + .baseline_artifact_id + .as_deref() + .context("MCTS requires a baseline gate identity")?; + let gate_revision = store + .get_registry_revision(gate_id) + .with_context(|| format!("MCTS baseline gate registry revision {gate_id} is missing"))?; + if gate_revision.registry_kind != CEX_BASELINE_GATE_REGISTRY_KIND + || gate_revision.revision_id != gate_id + { + bail!("MCTS baseline gate registry kind or identity is invalid"); + } + let gate: CexBaselineGateV1 = serde_json::from_value(gate_revision.payload.clone()) + .context("MCTS baseline gate payload is invalid")?; + gate.validate().map_err(anyhow::Error::msg)?; + + let factor_bank_revision = store + .get_registry_revision(&gate.factor_bank_revision_id) + .with_context(|| { + format!( + "MCTS Factor Bank registry revision {} is missing", + gate.factor_bank_revision_id + ) + })?; + if factor_bank_revision.registry_kind != CEX_FACTOR_BANK_REGISTRY_KIND + || factor_bank_revision.revision_id != gate.factor_bank_revision_id + || factor_bank_revision.parent_revision_id.as_deref() + != Some(mission.dataset_manifest_id.as_str()) + { + bail!("MCTS Factor Bank registry binding drifted"); + } + if gate_revision.parent_revision_id.as_deref() != Some(gate.factor_bank_revision_id.as_str()) { + bail!("MCTS baseline gate parent revision drifted"); + } + let factor_bank: CexFactorBankRevisionV2 = + serde_json::from_value(factor_bank_revision.payload.clone()) + .context("MCTS Factor Bank payload is invalid")?; + factor_bank.validate().map_err(anyhow::Error::msg)?; + if factor_bank.research_dataset != *research_dataset + || factor_bank.walk_forward_partition != *walk_forward_partition + { + bail!("MCTS Factor Bank research identity drifted"); + } + let control_revision = store + .get_registry_revision(&gate.mission_id) + .with_context(|| { + format!( + "MCTS source research mission registry revision {} is missing", + gate.mission_id + ) + })?; + if control_revision.registry_kind != "cex_research_mission" + || control_revision.revision_id != gate.mission_id + || control_revision.parent_revision_id.as_deref() + != Some(mission.dataset_manifest_id.as_str()) + { + bail!("MCTS source research mission registry binding drifted"); + } + let control_mission: CexResearchMissionArtifactV1 = + serde_json::from_value(control_revision.payload.clone()) + .context("MCTS source research mission payload is invalid")?; + control_mission.validate().map_err(anyhow::Error::msg)?; + if control_mission.semantic_id().map_err(anyhow::Error::msg)? != gate.mission_id { + bail!("MCTS source research mission identity drifted"); + } + + let ridge = match gate.ridge_artifact_id.as_deref() { + Some(ridge_id) => Some(read_mcts_baseline_artifact( + store, + ridge_id, + CEX_BASELINE_RIDGE_REGISTRY_KIND, + &factor_bank, + &control_mission, + research_dataset, + walk_forward_partition, + evaluation_protocol_hash, + CexBaselineModelKindV1::Ridge, + )?), + None => None, + }; + let cart = match gate.cart_artifact_id.as_deref() { + Some(cart_id) => Some(read_mcts_baseline_artifact( + store, + cart_id, + CEX_BASELINE_CART_REGISTRY_KIND, + &factor_bank, + &control_mission, + research_dataset, + walk_forward_partition, + evaluation_protocol_hash, + CexBaselineModelKindV1::ShallowCart, + )?), + None => None, + }; + gate.validate_binding(&factor_bank, ridge.as_ref(), cart.as_ref()) + .map_err(anyhow::Error::msg)?; + if !gate.passed { + bail!("MCTS baseline gate did not pass"); + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn read_mcts_baseline_artifact( + store: &AlphaStore, + artifact_id: &str, + registry_kind: &str, + factor_bank: &CexFactorBankRevisionV2, + control_mission: &CexResearchMissionArtifactV1, + research_dataset: &CexResearchContentRefV1, + walk_forward_partition: &CexResearchContentRefV1, + evaluation_protocol_hash: &str, + model_kind: CexBaselineModelKindV1, +) -> anyhow::Result { + let revision = store.get_registry_revision(artifact_id).with_context(|| { + format!("MCTS baseline artifact registry revision {artifact_id} is missing") + })?; + if revision.registry_kind != registry_kind + || revision.revision_id != artifact_id + || revision.parent_revision_id.as_deref() != Some(factor_bank.revision_id.as_str()) + { + bail!("MCTS baseline artifact registry binding drifted"); + } + let artifact: CexBaselineArtifactV1 = serde_json::from_value(revision.payload.clone()) + .context("MCTS baseline artifact payload is invalid")?; + artifact.validate().map_err(anyhow::Error::msg)?; + artifact + .validate_binding(control_mission, factor_bank) + .map_err(anyhow::Error::msg)?; + if artifact.model_kind != model_kind + || artifact.mission_id != control_mission.semantic_id().map_err(anyhow::Error::msg)? + || artifact.factor_bank_revision_id != factor_bank.revision_id + || artifact.research_dataset != *research_dataset + || artifact.walk_forward_partition != *walk_forward_partition + || artifact.evaluation_policy.content_sha256 != evaluation_protocol_hash + || artifact.evaluation.protocol_binding()?.1 != evaluation_protocol_hash + { + bail!("MCTS baseline artifact identity drifted"); + } + Ok(artifact) +} + pub fn mission_status(args: MissionStatusArgs) -> anyhow::Result<()> { let store = AlphaStore::open(&args.db)?; let lineage = store.mission_lineage(&args.mission_id)?; @@ -191,8 +347,6 @@ fn build_engine( args: &RunMissionArgs, dataset: &alpha_engine::evaluation::PreparedDataset, mission: &ResearchMission, - evaluation_protocol_hash: &str, - evaluator_config_hash: &str, governed_gp: Option<(&CexGpPolicyV1, &str)>, ) -> anyhow::Result> { validate_live_mission_args(args)?; @@ -215,7 +369,6 @@ fn build_engine( let fields = fields.into_iter().collect::>(); validate_live_feature_fields(&fields)?; let primary = fields[0].clone(); - let secondary = fields.get(1).cloned().unwrap_or_else(|| primary.clone()); let engine: Box = match args.engine { EngineChoice::Gp => match governed_gp { Some((policy, candidate_namespace)) => { @@ -238,22 +391,8 @@ fn build_engine( .map_err(anyhow::Error::msg)?, ), }, - EngineChoice::Mcts => Box::new( - MctsEngine::new_live_bound( - args.seed, - primary.clone(), - secondary, - 1.414, - 5, - CexMctsSearchIdentityV1::for_mission( - mission, - evaluation_protocol_hash.to_string(), - evaluator_config_hash.to_string(), - args.max_new_iterations, - ) - .map_err(anyhow::Error::msg)?, - ) - .map_err(anyhow::Error::msg)?, + EngineChoice::Mcts => bail!( + "MCTS candidate space {CEX_MCTS_CANDIDATE_SPACE_ID} is not authorized for CEX Factor Bank missions; Factor-Bank subset adapter (#601) is required" ), EngineChoice::Bayesian => bail!(BAYESIAN_WINDOW_SEARCH_LIVE_CAPABILITY_ERROR), EngineChoice::OfflineRl => { @@ -337,6 +476,9 @@ fn live_event_domain_name(domain: LiveEventDomain) -> &'static str { mod tests { use super::*; use crate::cli::{DatasetArgs, ValidationArgs}; + use alpha_domain::{MissionCompletionPolicy, SearchBudget, ValidatorMode}; + use chrono::Utc; + use hft_research_manifest::ManifestId; use std::path::PathBuf; #[test] @@ -351,6 +493,49 @@ mod tests { ); } + #[test] + fn mcts_shared_fan_in_requires_an_immutable_baseline_gate() { + let store = AlphaStore::open_in_memory().unwrap(); + let mission = ResearchMission { + mission_id: "consumer-mission".to_string(), + objective: "test objective".to_string(), + hypothesis_scope: "test hypothesis".to_string(), + mutable_scope: vec!["factor_ast".to_string()], + dataset_manifest_id: ManifestId::new("dataset-1").unwrap(), + baseline_artifact_id: None, + validation_mode: ValidatorMode::MissionValidator, + validator_spec: serde_json::json!({}), + search_budget: SearchBudget { + max_candidates: 1, + max_expansions: 1, + max_tokens: 0, + max_seconds: 1, + }, + completion_policy: MissionCompletionPolicy::default(), + prompt_snapshot_id: None, + search_policy_snapshot_id: "policy-1".to_string(), + status: MissionStatus::Pending, + terminal_reason: None, + created_at: Utc::now(), + updated_at: Utc::now(), + }; + let reference = |id: &str| CexResearchContentRefV1 { + id: id.to_string(), + content_sha256: "a".repeat(64), + }; + + let error = validate_mcts_baseline_gate( + &store, + &mission, + &reference("cex-research-dataset-1"), + &reference("cex-walk-forward-partition-1"), + &"b".repeat(64), + ) + .unwrap_err(); + + assert!(error.to_string().contains("baseline gate identity")); + } + #[test] fn mission_preflight_rejects_live_capability_before_opening_store() { let cases = [ From fb6f892b2ae4e3cdc6cfde3c616b1c997e2d8ff1 Mon Sep 17 00:00:00 2001 From: Sonic Shih Date: Tue, 4 Aug 2026 05:33:24 +0800 Subject: [PATCH 4/4] fix(alpha): replay baseline evidence before MCTS (#600) --- rust_hft/alpha-harness/app/src/mission.rs | 33 ++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/rust_hft/alpha-harness/app/src/mission.rs b/rust_hft/alpha-harness/app/src/mission.rs index 04046edb..7df381ae 100644 --- a/rust_hft/alpha-harness/app/src/mission.rs +++ b/rust_hft/alpha-harness/app/src/mission.rs @@ -4,12 +4,13 @@ use crate::{ }; use alpha_domain::{ canonical_json_hash, CexBaselineArtifactV1, CexBaselineGateV1, CexBaselineModelKindV1, - CexFactorBankRevisionV2, CexGpPolicyV1, CexResearchContentRefV1, CexResearchMissionArtifactV1, - MissionStatus, ResearchMission, + CexBaselinePolicyV1, CexFactorBankRevisionV2, CexGpPolicyV1, CexResearchContentRefV1, + CexResearchMissionArtifactV1, MissionStatus, ResearchMission, }; use alpha_engine::{ + baselines::verify_cex_baseline_artifact, engines::{GeneticProgrammingEngine, OfflineRlEngine, OfflineTrace}, - evaluation::prepare_dataset, + evaluation::{prepare_dataset, EngineContext}, formula_evaluator::FormulaEvaluator, learning::{close_learning_loop, FailureCritic, LearningConfig}, llm::{LlmConfig, LlmProposalEngine, OpenAiCompatibleClient}, @@ -114,6 +115,7 @@ fn execute_mission_inner( validate_mcts_baseline_gate( &store, &mission, + Some(&research_context), &research_dataset, &walk_forward_partition, &evaluation_protocol_hash, @@ -152,6 +154,7 @@ fn execute_mission_inner( fn validate_mcts_baseline_gate( store: &AlphaStore, mission: &ResearchMission, + research_context: Option<&EngineContext<'_>>, research_dataset: &CexResearchContentRefV1, walk_forward_partition: &CexResearchContentRefV1, evaluation_protocol_hash: &str, @@ -221,6 +224,9 @@ fn validate_mcts_baseline_gate( if control_mission.semantic_id().map_err(anyhow::Error::msg)? != gate.mission_id { bail!("MCTS source research mission identity drifted"); } + let baseline_policy = + CexBaselinePolicyV1::controlled_v1(control_mission.spec.policies.baseline.id.clone())?; + baseline_policy.validate_binding(&control_mission.spec.policies.baseline)?; let ridge = match gate.ridge_artifact_id.as_deref() { Some(ridge_id) => Some(read_mcts_baseline_artifact( @@ -229,6 +235,8 @@ fn validate_mcts_baseline_gate( CEX_BASELINE_RIDGE_REGISTRY_KIND, &factor_bank, &control_mission, + &baseline_policy, + research_context.context("MCTS baseline artifact verification context is missing")?, research_dataset, walk_forward_partition, evaluation_protocol_hash, @@ -243,6 +251,8 @@ fn validate_mcts_baseline_gate( CEX_BASELINE_CART_REGISTRY_KIND, &factor_bank, &control_mission, + &baseline_policy, + research_context.context("MCTS baseline artifact verification context is missing")?, research_dataset, walk_forward_partition, evaluation_protocol_hash, @@ -250,8 +260,14 @@ fn validate_mcts_baseline_gate( )?), None => None, }; - gate.validate_binding(&factor_bank, ridge.as_ref(), cart.as_ref()) - .map_err(anyhow::Error::msg)?; + gate.validate_binding( + &control_mission, + &baseline_policy, + &factor_bank, + ridge.as_ref(), + cart.as_ref(), + ) + .map_err(anyhow::Error::msg)?; if !gate.passed { bail!("MCTS baseline gate did not pass"); } @@ -265,6 +281,8 @@ fn read_mcts_baseline_artifact( registry_kind: &str, factor_bank: &CexFactorBankRevisionV2, control_mission: &CexResearchMissionArtifactV1, + baseline_policy: &CexBaselinePolicyV1, + research_context: &EngineContext<'_>, research_dataset: &CexResearchContentRefV1, walk_forward_partition: &CexResearchContentRefV1, evaluation_protocol_hash: &str, @@ -283,7 +301,7 @@ fn read_mcts_baseline_artifact( .context("MCTS baseline artifact payload is invalid")?; artifact.validate().map_err(anyhow::Error::msg)?; artifact - .validate_binding(control_mission, factor_bank) + .validate_binding(control_mission, baseline_policy, factor_bank) .map_err(anyhow::Error::msg)?; if artifact.model_kind != model_kind || artifact.mission_id != control_mission.semantic_id().map_err(anyhow::Error::msg)? @@ -295,6 +313,8 @@ fn read_mcts_baseline_artifact( { bail!("MCTS baseline artifact identity drifted"); } + verify_cex_baseline_artifact(research_context, factor_bank, &artifact) + .map_err(anyhow::Error::msg)?; Ok(artifact) } @@ -527,6 +547,7 @@ mod tests { let error = validate_mcts_baseline_gate( &store, &mission, + None, &reference("cex-research-dataset-1"), &reference("cex-walk-forward-partition-1"), &"b".repeat(64),