diff --git a/Cargo.lock b/Cargo.lock index 12982909..ebc851d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8267,6 +8267,9 @@ dependencies = [ "criterion 0.5.1", "proptest", "rand 0.8.5", + "rvf-crypto", + "rvf-types", + "rvf-wire", "serde", "serde_json", "thiserror 2.0.17", diff --git a/crates/ruvector-domain-expansion-wasm/Cargo.toml b/crates/ruvector-domain-expansion-wasm/Cargo.toml index 2abdcc27..09a7847b 100644 --- a/crates/ruvector-domain-expansion-wasm/Cargo.toml +++ b/crates/ruvector-domain-expansion-wasm/Cargo.toml @@ -11,6 +11,10 @@ keywords = ["wasm", "transfer-learning", "domain-expansion", "generalization"] categories = ["wasm", "algorithms", "science"] rust-version = "1.70" +[features] +default = ["rvf"] +rvf = ["ruvector-domain-expansion/rvf"] + [lib] crate-type = ["cdylib", "rlib"] diff --git a/crates/ruvector-domain-expansion-wasm/src/lib.rs b/crates/ruvector-domain-expansion-wasm/src/lib.rs index 302041c8..001cc7f3 100644 --- a/crates/ruvector-domain-expansion-wasm/src/lib.rs +++ b/crates/ruvector-domain-expansion-wasm/src/lib.rs @@ -3,6 +3,10 @@ //! Provides JavaScript-accessible interfaces for cross-domain transfer learning, //! Meta Thompson Sampling, PolicyKernel population search, and the acceleration //! scoreboard. All domain operations run at native speed in the browser/edge. +//! +//! With the `rvf` feature (default), includes RVF segment serialization for +//! packaging transfer priors, policy kernels, and cost curves into the +//! RuVector Format wire protocol. use ruvector_domain_expansion::{ AccelerationScoreboard, ArmId, ContextBucket, CostCurve, @@ -372,3 +376,149 @@ impl WasmScoreboard { serde_wasm_bindgen::to_value(&s).unwrap_or(JsValue::NULL) } } + +// ─── RVF Bridge Exports ───────────────────────────────────────────────────── + +/// WASM-exported RVF bridge for segment serialization and witness chains. +/// +/// Enables packaging domain expansion artifacts into RVF wire format +/// for embedding in RVF files and AGI containers. +#[cfg(feature = "rvf")] +#[wasm_bindgen] +pub struct WasmRvfBridge; + +#[cfg(feature = "rvf")] +#[wasm_bindgen] +impl WasmRvfBridge { + /// Create a new RVF bridge instance. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self + } + + /// Serialize a TransferPrior (JSON) into an RVF TRANSFER_PRIOR segment. + /// Returns the raw segment bytes. + #[wasm_bindgen(js_name = transferPriorToSegment)] + pub fn transfer_prior_to_segment( + &self, + prior_json: &str, + segment_id: u64, + ) -> Result, JsValue> { + let prior: ruvector_domain_expansion::TransferPrior = + serde_json::from_str(prior_json) + .map_err(|e| JsValue::from_str(&format!("JSON parse error: {e}")))?; + Ok(ruvector_domain_expansion::rvf_bridge::transfer_prior_to_segment( + &prior, segment_id, + )) + } + + /// Deserialize a TransferPrior from RVF segment bytes. Returns JSON. + #[wasm_bindgen(js_name = transferPriorFromSegment)] + pub fn transfer_prior_from_segment(&self, data: &[u8]) -> Result { + let prior = ruvector_domain_expansion::rvf_bridge::transfer_prior_from_segment(data) + .map_err(|e| JsValue::from_str(&format!("RVF decode error: {e}")))?; + serde_json::to_string(&prior) + .map_err(|e| JsValue::from_str(&format!("JSON serialize error: {e}"))) + } + + /// Serialize a PolicyKernel (JSON) into an RVF POLICY_KERNEL segment. + #[wasm_bindgen(js_name = policyKernelToSegment)] + pub fn policy_kernel_to_segment( + &self, + kernel_json: &str, + segment_id: u64, + ) -> Result, JsValue> { + let kernel: ruvector_domain_expansion::PolicyKernel = + serde_json::from_str(kernel_json) + .map_err(|e| JsValue::from_str(&format!("JSON parse error: {e}")))?; + Ok(ruvector_domain_expansion::rvf_bridge::policy_kernel_to_segment( + &kernel, segment_id, + )) + } + + /// Serialize a CostCurve (JSON) into an RVF COST_CURVE segment. + #[wasm_bindgen(js_name = costCurveToSegment)] + pub fn cost_curve_to_segment( + &self, + curve_json: &str, + segment_id: u64, + ) -> Result, JsValue> { + let curve: ruvector_domain_expansion::CostCurve = + serde_json::from_str(curve_json) + .map_err(|e| JsValue::from_str(&format!("JSON parse error: {e}")))?; + Ok(ruvector_domain_expansion::rvf_bridge::cost_curve_to_segment( + &curve, segment_id, + )) + } + + /// Compute the SHAKE-256 witness hash for a TransferPrior. + /// Returns 32 bytes (hex-encoded string). + #[wasm_bindgen(js_name = computeWitnessHash)] + pub fn compute_witness_hash(&self, prior_json: &str) -> Result { + let prior: ruvector_domain_expansion::TransferPrior = + serde_json::from_str(prior_json) + .map_err(|e| JsValue::from_str(&format!("JSON parse error: {e}")))?; + let hash = ruvector_domain_expansion::rvf_bridge::compute_transfer_witness_hash(&prior); + Ok(hash.iter().map(|b| format!("{b:02x}")).collect()) + } + + /// Assemble multiple domain expansion artifacts into a concatenated + /// RVF segment byte stream. Input: JSON object with `priors`, `kernels`, + /// `curves` arrays and `base_segment_id`. + #[wasm_bindgen(js_name = assembleSegments)] + pub fn assemble_segments( + &self, + priors_json: &str, + kernels_json: &str, + curves_json: &str, + base_segment_id: u64, + ) -> Result, JsValue> { + let priors: Vec = + serde_json::from_str(priors_json) + .map_err(|e| JsValue::from_str(&format!("priors parse error: {e}")))?; + let kernels: Vec = + serde_json::from_str(kernels_json) + .map_err(|e| JsValue::from_str(&format!("kernels parse error: {e}")))?; + let curves: Vec = + serde_json::from_str(curves_json) + .map_err(|e| JsValue::from_str(&format!("curves parse error: {e}")))?; + + Ok(ruvector_domain_expansion::rvf_bridge::assemble_domain_expansion_segments( + &priors, + &kernels, + &curves, + base_segment_id, + )) + } + + /// Extract solver-compatible prior exchange data from a TransferPrior JSON. + /// Returns JSON array of SolverPriorExchange objects. + #[wasm_bindgen(js_name = extractSolverPriors)] + pub fn extract_solver_priors( + &self, + domain_id: &str, + prior_json: &str, + ) -> Result { + // Build a temporary Thompson engine with the prior + let prior: ruvector_domain_expansion::TransferPrior = + serde_json::from_str(prior_json) + .map_err(|e| JsValue::from_str(&format!("JSON parse error: {e}")))?; + + let arms: Vec = prior + .bucket_priors + .values() + .flat_map(|arms| arms.keys().map(|a| a.0.clone())) + .collect::>() + .into_iter() + .collect(); + + let mut engine = ruvector_domain_expansion::MetaThompsonEngine::new(arms); + let domain = DomainId(domain_id.to_string()); + engine.init_domain_with_transfer(domain.clone(), &prior); + + let exchanges = + ruvector_domain_expansion::rvf_bridge::extract_solver_priors(&engine, &domain); + serde_json::to_string(&exchanges) + .map_err(|e| JsValue::from_str(&format!("JSON serialize error: {e}"))) + } +} diff --git a/crates/ruvector-domain-expansion/Cargo.toml b/crates/ruvector-domain-expansion/Cargo.toml index c760dfed..3b810626 100644 --- a/crates/ruvector-domain-expansion/Cargo.toml +++ b/crates/ruvector-domain-expansion/Cargo.toml @@ -10,12 +10,21 @@ description = "Cross-domain transfer learning engine: Rust synthesis, structured keywords = ["transfer-learning", "domain-expansion", "generalization", "rust-synthesis", "planning"] categories = ["algorithms", "science"] +[features] +default = [] +rvf = ["dep:rvf-types", "dep:rvf-wire", "dep:rvf-crypto"] + [dependencies] serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } rand = { workspace = true } +# RVF integration (optional, behind "rvf" feature) +rvf-types = { version = "0.1.0", path = "../rvf/rvf-types", optional = true } +rvf-wire = { version = "0.1.0", path = "../rvf/rvf-wire", optional = true } +rvf-crypto = { version = "0.1.0", path = "../rvf/rvf-crypto", optional = true } + [dev-dependencies] proptest = { workspace = true } criterion = { workspace = true } diff --git a/crates/ruvector-domain-expansion/src/lib.rs b/crates/ruvector-domain-expansion/src/lib.rs index 0a391c0a..b3882419 100644 --- a/crates/ruvector-domain-expansion/src/lib.rs +++ b/crates/ruvector-domain-expansion/src/lib.rs @@ -51,6 +51,12 @@ pub mod rust_synthesis; pub mod tool_orchestration; pub mod transfer; +/// RVF format integration: segment serialization, witness chains, AGI packaging. +/// +/// Requires the `rvf` feature to be enabled. +#[cfg(feature = "rvf")] +pub mod rvf_bridge; + // Re-export core types. pub use cost_curve::{ AccelerationEntry, AccelerationScoreboard, ConvergenceThresholds, CostCurve, CostCurvePoint, diff --git a/crates/ruvector-domain-expansion/src/rvf_bridge.rs b/crates/ruvector-domain-expansion/src/rvf_bridge.rs new file mode 100644 index 00000000..04f0720d --- /dev/null +++ b/crates/ruvector-domain-expansion/src/rvf_bridge.rs @@ -0,0 +1,737 @@ +//! RVF Integration Bridge +//! +//! Connects the domain expansion engine to the RuVector Format (RVF): +//! - Serializes `TransferPrior`, `PolicyKernel`, `CostCurve` into RVF segments +//! - Creates SHAKE-256 witness chains for transfer verification +//! - Packages domain expansion artifacts into AGI container TLV entries +//! - Bridges priors to/from the rvf-solver-wasm `PolicyKernel` +//! +//! Requires the `rvf` feature to be enabled. + +use rvf_types::{SegmentFlags, SegmentType}; +use rvf_wire::writer::write_segment; +use rvf_wire::reader::{read_segment, validate_segment}; + +use crate::cost_curve::{AccelerationScoreboard, CostCurve}; +use crate::domain::DomainId; +use crate::policy_kernel::PolicyKernel; +use crate::transfer::{ArmId, BetaParams, ContextBucket, MetaThompsonEngine, TransferPrior}; + +// ─── Wire-format wrappers ─────────────────────────────────────────────────── +// +// JSON requires string keys for objects. TransferPrior uses HashMap +// which can't be directly serialized. These wrappers convert to/from Vec<(K,V)> form. + +/// Wire-format representation of a TransferPrior (JSON-safe). +#[derive(serde::Serialize, serde::Deserialize)] +struct WireTransferPrior { + source_domain: DomainId, + bucket_priors: Vec<(ContextBucket, Vec<(ArmId, BetaParams)>)>, + cost_ema_priors: Vec<(ContextBucket, f32)>, + training_cycles: u64, + witness_hash: String, +} + +impl From<&TransferPrior> for WireTransferPrior { + fn from(p: &TransferPrior) -> Self { + Self { + source_domain: p.source_domain.clone(), + bucket_priors: p + .bucket_priors + .iter() + .map(|(b, arms)| { + let arm_vec: Vec<(ArmId, BetaParams)> = + arms.iter().map(|(a, bp)| (a.clone(), bp.clone())).collect(); + (b.clone(), arm_vec) + }) + .collect(), + cost_ema_priors: p + .cost_ema_priors + .iter() + .map(|(b, c)| (b.clone(), *c)) + .collect(), + training_cycles: p.training_cycles, + witness_hash: p.witness_hash.clone(), + } + } +} + +impl From for TransferPrior { + fn from(w: WireTransferPrior) -> Self { + let mut bucket_priors = std::collections::HashMap::new(); + for (bucket, arms) in w.bucket_priors { + let arm_map: std::collections::HashMap = + arms.into_iter().collect(); + bucket_priors.insert(bucket, arm_map); + } + let cost_ema_priors: std::collections::HashMap = + w.cost_ema_priors.into_iter().collect(); + Self { + source_domain: w.source_domain, + bucket_priors, + cost_ema_priors, + training_cycles: w.training_cycles, + witness_hash: w.witness_hash, + } + } +} + +/// Wire-format representation of a PolicyKernel (JSON-safe). +#[derive(serde::Serialize, serde::Deserialize)] +struct WirePolicyKernel { + id: String, + knobs: crate::policy_kernel::PolicyKnobs, + holdout_scores: Vec<(DomainId, f32)>, + total_cost: f32, + cycles: u64, + generation: u32, + parent_id: Option, + replay_verified: bool, +} + +impl From<&PolicyKernel> for WirePolicyKernel { + fn from(k: &PolicyKernel) -> Self { + Self { + id: k.id.clone(), + knobs: k.knobs.clone(), + holdout_scores: k + .holdout_scores + .iter() + .map(|(d, s)| (d.clone(), *s)) + .collect(), + total_cost: k.total_cost, + cycles: k.cycles, + generation: k.generation, + parent_id: k.parent_id.clone(), + replay_verified: k.replay_verified, + } + } +} + +impl From for PolicyKernel { + fn from(w: WirePolicyKernel) -> Self { + Self { + id: w.id, + knobs: w.knobs, + holdout_scores: w.holdout_scores.into_iter().collect(), + total_cost: w.total_cost, + cycles: w.cycles, + generation: w.generation, + parent_id: w.parent_id, + replay_verified: w.replay_verified, + } + } +} + +// ─── Segment serialization ────────────────────────────────────────────────── + +/// Serialize a `TransferPrior` into an RVF TRANSFER_PRIOR segment. +/// +/// Wire format: JSON payload (using Vec-of-tuples for map keys) inside a +/// 64-byte-aligned RVF segment. Type: `SegmentType::TransferPrior` (0x30). +pub fn transfer_prior_to_segment(prior: &TransferPrior, segment_id: u64) -> Vec { + let wire: WireTransferPrior = prior.into(); + let payload = serde_json::to_vec(&wire).expect("WireTransferPrior serialization cannot fail"); + write_segment( + SegmentType::TransferPrior as u8, + &payload, + SegmentFlags::empty(), + segment_id, + ) +} + +/// Deserialize a `TransferPrior` from an RVF segment's raw bytes. +/// +/// Validates the segment header, checks the content hash, and deserializes +/// the JSON payload. +pub fn transfer_prior_from_segment(data: &[u8]) -> Result { + let (header, payload) = read_segment(data).map_err(RvfBridgeError::Rvf)?; + if header.seg_type != SegmentType::TransferPrior as u8 { + return Err(RvfBridgeError::WrongSegmentType { + expected: SegmentType::TransferPrior as u8, + got: header.seg_type, + }); + } + validate_segment(&header, payload).map_err(RvfBridgeError::Rvf)?; + let wire: WireTransferPrior = + serde_json::from_slice(payload).map_err(RvfBridgeError::Json)?; + Ok(wire.into()) +} + +/// Serialize a `PolicyKernel` into an RVF POLICY_KERNEL segment. +pub fn policy_kernel_to_segment(kernel: &PolicyKernel, segment_id: u64) -> Vec { + let wire: WirePolicyKernel = kernel.into(); + let payload = serde_json::to_vec(&wire).expect("WirePolicyKernel serialization cannot fail"); + write_segment( + SegmentType::PolicyKernel as u8, + &payload, + SegmentFlags::empty(), + segment_id, + ) +} + +/// Deserialize a `PolicyKernel` from an RVF segment. +pub fn policy_kernel_from_segment(data: &[u8]) -> Result { + let (header, payload) = read_segment(data).map_err(RvfBridgeError::Rvf)?; + if header.seg_type != SegmentType::PolicyKernel as u8 { + return Err(RvfBridgeError::WrongSegmentType { + expected: SegmentType::PolicyKernel as u8, + got: header.seg_type, + }); + } + validate_segment(&header, payload).map_err(RvfBridgeError::Rvf)?; + let wire: WirePolicyKernel = + serde_json::from_slice(payload).map_err(RvfBridgeError::Json)?; + Ok(wire.into()) +} + +/// Serialize a `CostCurve` into an RVF COST_CURVE segment. +pub fn cost_curve_to_segment(curve: &CostCurve, segment_id: u64) -> Vec { + let payload = serde_json::to_vec(curve).expect("CostCurve serialization cannot fail"); + write_segment( + SegmentType::CostCurve as u8, + &payload, + SegmentFlags::empty(), + segment_id, + ) +} + +/// Deserialize a `CostCurve` from an RVF segment. +pub fn cost_curve_from_segment(data: &[u8]) -> Result { + let (header, payload) = read_segment(data).map_err(RvfBridgeError::Rvf)?; + if header.seg_type != SegmentType::CostCurve as u8 { + return Err(RvfBridgeError::WrongSegmentType { + expected: SegmentType::CostCurve as u8, + got: header.seg_type, + }); + } + validate_segment(&header, payload).map_err(RvfBridgeError::Rvf)?; + serde_json::from_slice(payload).map_err(RvfBridgeError::Json) +} + +// ─── Witness chain ────────────────────────────────────────────────────────── + +/// Witness type constants for domain expansion operations. +pub const WITNESS_TRANSFER: u8 = 0x10; +/// Witness type for policy kernel promotion. +pub const WITNESS_POLICY_PROMOTION: u8 = 0x11; +/// Witness type for cost curve convergence checkpoint. +pub const WITNESS_CONVERGENCE: u8 = 0x12; + +/// Create a SHAKE-256 witness hash for a transfer prior. +/// +/// The witness hash covers: source domain, training cycles, and the serialized +/// bucket priors. This replaces the old string-based `witness_hash` field. +pub fn compute_transfer_witness_hash(prior: &TransferPrior) -> [u8; 32] { + let wire: WireTransferPrior = prior.into(); + let payload = + serde_json::to_vec(&wire).expect("WireTransferPrior serialization cannot fail"); + rvf_crypto::shake256_256(&payload) +} + +/// Build witness entries for a transfer verification event. +/// +/// Returns entries suitable for `rvf_crypto::create_witness_chain()`. +pub fn build_transfer_witness_entries( + prior: &TransferPrior, + source: &DomainId, + target: &DomainId, + acceleration_factor: f32, + timestamp_ns: u64, +) -> Vec { + let mut entries = Vec::with_capacity(2); + + // Entry 1: Transfer prior hash + let prior_hash = compute_transfer_witness_hash(prior); + entries.push(rvf_crypto::WitnessEntry { + prev_hash: [0u8; 32], + action_hash: prior_hash, + timestamp_ns, + witness_type: WITNESS_TRANSFER, + }); + + // Entry 2: Acceleration verification (hash of source→target + factor) + let accel_payload = format!( + "{}->{}:accel={:.6}", + source.0, target.0, acceleration_factor + ); + let accel_hash = rvf_crypto::shake256_256(accel_payload.as_bytes()); + entries.push(rvf_crypto::WitnessEntry { + prev_hash: [0u8; 32], // chaining handled by create_witness_chain + action_hash: accel_hash, + timestamp_ns: timestamp_ns + 1, + witness_type: WITNESS_CONVERGENCE, + }); + + entries +} + +// ─── AGI Container TLV packaging ──────────────────────────────────────────── + +/// A TLV (Tag-Length-Value) entry for AGI container manifest packaging. +#[derive(Debug, Clone)] +pub struct AgiTlvEntry { + /// TLV tag (see `AGI_TAG_*` constants in rvf-types). + pub tag: u16, + /// Serialized value payload. + pub value: Vec, +} + +/// Package domain expansion artifacts into AGI container TLV entries. +/// +/// Returns a vector of TLV entries ready for inclusion in an AGI container +/// manifest segment. Each entry uses the corresponding `AGI_TAG_*` constant. +pub fn package_for_agi_container( + priors: &[TransferPrior], + kernels: &[PolicyKernel], + scoreboard: &AccelerationScoreboard, +) -> Vec { + let mut entries = Vec::new(); + + // Transfer priors (use wire format for JSON-safe serialization) + for prior in priors { + let wire: WireTransferPrior = prior.into(); + let value = serde_json::to_vec(&wire).expect("WireTransferPrior serialization cannot fail"); + entries.push(AgiTlvEntry { + tag: rvf_types::AGI_TAG_TRANSFER_PRIOR, + value, + }); + } + + // Policy kernels (use wire format for JSON-safe serialization) + for kernel in kernels { + let wire: WirePolicyKernel = kernel.into(); + let value = serde_json::to_vec(&wire).expect("WirePolicyKernel serialization cannot fail"); + entries.push(AgiTlvEntry { + tag: rvf_types::AGI_TAG_POLICY_KERNEL, + value, + }); + } + + // Cost curves from the scoreboard + for curve in scoreboard.curves.values() { + let value = serde_json::to_vec(curve).expect("CostCurve serialization cannot fail"); + entries.push(AgiTlvEntry { + tag: rvf_types::AGI_TAG_COST_CURVE, + value, + }); + } + + entries +} + +/// Encode TLV entries into a binary payload for inclusion in a META segment. +/// +/// Wire format per entry: `[tag: u16 LE][length: u32 LE][value: length bytes]` +pub fn encode_tlv_entries(entries: &[AgiTlvEntry]) -> Vec { + let total_size: usize = entries.iter().map(|e| 6 + e.value.len()).sum(); + let mut buf = Vec::with_capacity(total_size); + for entry in entries { + buf.extend_from_slice(&entry.tag.to_le_bytes()); + buf.extend_from_slice(&(entry.value.len() as u32).to_le_bytes()); + buf.extend_from_slice(&entry.value); + } + buf +} + +/// Decode TLV entries from a binary payload. +pub fn decode_tlv_entries(data: &[u8]) -> Result, RvfBridgeError> { + let mut entries = Vec::new(); + let mut offset = 0; + while offset + 6 <= data.len() { + let tag = u16::from_le_bytes([data[offset], data[offset + 1]]); + let length = u32::from_le_bytes([ + data[offset + 2], + data[offset + 3], + data[offset + 4], + data[offset + 5], + ]) as usize; + offset += 6; + if offset + length > data.len() { + return Err(RvfBridgeError::TruncatedTlv); + } + entries.push(AgiTlvEntry { + tag, + value: data[offset..offset + length].to_vec(), + }); + offset += length; + } + Ok(entries) +} + +// ─── Solver bridge ────────────────────────────────────────────────────────── + +/// Compact prior exchange format bridging domain expansion's `MetaThompsonEngine` +/// to the rvf-solver-wasm `PolicyKernel`. +/// +/// The solver-wasm uses per-bucket `SkipModeStats` with `(alpha_safety, beta_safety)` +/// and `cost_ema`. The domain expansion uses per-bucket `BetaParams` with +/// `(alpha, beta)` and `cost_ema_priors`. This type converts between them. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SolverPriorExchange { + /// Context bucket key (e.g. "medium:some:clean"). + pub bucket_key: String, + /// Per-arm alpha/beta pairs mapping arm name to (alpha, beta). + pub arm_params: Vec<(String, f32, f32)>, + /// Cost EMA for this bucket. + pub cost_ema: f32, + /// Training cycle count for confidence estimation. + pub training_cycles: u64, +} + +/// Extract solver-compatible prior exchange data from the Thompson engine. +/// +/// Flattens the domain expansion's hierarchical buckets into the solver's +/// flat "range:distractor:noise" keys for the specified domain. +pub fn extract_solver_priors( + engine: &MetaThompsonEngine, + domain_id: &DomainId, +) -> Vec { + let prior = match engine.extract_prior(domain_id) { + Some(p) => p, + None => return Vec::new(), + }; + + prior + .bucket_priors + .iter() + .map(|(bucket, arms)| { + let bucket_key = format!("{}:{}", bucket.difficulty_tier, bucket.category); + let arm_params: Vec<(String, f32, f32)> = arms + .iter() + .map(|(arm, params)| (arm.0.clone(), params.alpha, params.beta)) + .collect(); + let cost_ema = prior + .cost_ema_priors + .get(bucket) + .copied() + .unwrap_or(1.0); + + SolverPriorExchange { + bucket_key, + arm_params, + cost_ema, + training_cycles: prior.training_cycles, + } + }) + .collect() +} + +/// Import solver prior exchange data back into the Thompson engine. +/// +/// Seeds the specified domain with the exchanged priors, enabling +/// cross-system transfer. +pub fn import_solver_priors( + engine: &mut MetaThompsonEngine, + domain_id: &DomainId, + exchanges: &[SolverPriorExchange], +) { + // Build a synthetic TransferPrior from the exchange data. + let mut prior = TransferPrior::uniform(domain_id.clone()); + + for exchange in exchanges { + let parts: Vec<&str> = exchange.bucket_key.splitn(2, ':').collect(); + let bucket = ContextBucket { + difficulty_tier: parts.first().unwrap_or(&"medium").to_string(), + category: parts.get(1).unwrap_or(&"general").to_string(), + }; + + let mut arm_map = std::collections::HashMap::new(); + for (arm_name, alpha, beta) in &exchange.arm_params { + arm_map.insert( + crate::transfer::ArmId(arm_name.clone()), + BetaParams { + alpha: *alpha, + beta: *beta, + }, + ); + } + prior.bucket_priors.insert(bucket.clone(), arm_map); + prior.cost_ema_priors.insert(bucket, exchange.cost_ema); + prior.training_cycles = exchange.training_cycles; + } + + engine.init_domain_with_transfer(domain_id.clone(), &prior); +} + +// ─── Multi-segment file assembly ──────────────────────────────────────────── + +/// Assemble a complete RVF byte stream containing all domain expansion segments. +/// +/// Outputs concatenated segments: transfer priors, then policy kernels, then +/// cost curves. Each gets a unique segment ID starting from `base_segment_id`. +/// +/// The returned bytes can be appended to an existing RVF file or written as +/// a standalone domain expansion archive. +pub fn assemble_domain_expansion_segments( + priors: &[TransferPrior], + kernels: &[PolicyKernel], + curves: &[CostCurve], + base_segment_id: u64, +) -> Vec { + let mut buf = Vec::new(); + let mut seg_id = base_segment_id; + + for prior in priors { + buf.extend_from_slice(&transfer_prior_to_segment(prior, seg_id)); + seg_id += 1; + } + for kernel in kernels { + buf.extend_from_slice(&policy_kernel_to_segment(kernel, seg_id)); + seg_id += 1; + } + for curve in curves { + buf.extend_from_slice(&cost_curve_to_segment(curve, seg_id)); + seg_id += 1; + } + + buf +} + +// ─── Errors ───────────────────────────────────────────────────────────────── + +/// Errors specific to the RVF bridge operations. +#[derive(Debug)] +pub enum RvfBridgeError { + /// Underlying RVF format error. + Rvf(rvf_types::RvfError), + /// JSON serialization/deserialization error. + Json(serde_json::Error), + /// Segment type mismatch. + WrongSegmentType { + /// Expected segment type discriminant. + expected: u8, + /// Actual segment type discriminant. + got: u8, + }, + /// TLV payload truncated. + TruncatedTlv, +} + +impl std::fmt::Display for RvfBridgeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Rvf(e) => write!(f, "RVF error: {e}"), + Self::Json(e) => write!(f, "JSON error: {e}"), + Self::WrongSegmentType { expected, got } => { + write!(f, "wrong segment type: expected 0x{expected:02X}, got 0x{got:02X}") + } + Self::TruncatedTlv => write!(f, "TLV payload truncated"), + } + } +} + +impl std::error::Error for RvfBridgeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Json(e) => Some(e), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cost_curve::{CostCurvePoint, ConvergenceThresholds}; + + #[test] + fn transfer_prior_round_trip() { + let mut prior = TransferPrior::uniform(DomainId("test".into())); + let bucket = ContextBucket { + difficulty_tier: "medium".into(), + category: "algo".into(), + }; + prior.update_posterior( + bucket, + crate::transfer::ArmId("greedy".into()), + 0.85, + ); + + let segment = transfer_prior_to_segment(&prior, 1); + let decoded = transfer_prior_from_segment(&segment).unwrap(); + + assert_eq!(decoded.source_domain, prior.source_domain); + assert_eq!(decoded.training_cycles, prior.training_cycles); + } + + #[test] + fn policy_kernel_round_trip() { + let kernel = PolicyKernel::new("test_kernel".into()); + let segment = policy_kernel_to_segment(&kernel, 2); + let decoded = policy_kernel_from_segment(&segment).unwrap(); + + assert_eq!(decoded.id, "test_kernel"); + assert_eq!(decoded.generation, 0); + } + + #[test] + fn cost_curve_round_trip() { + let mut curve = CostCurve::new( + DomainId("test".into()), + ConvergenceThresholds::default(), + ); + curve.record(CostCurvePoint { + cycle: 0, + accuracy: 0.3, + cost_per_solve: 0.1, + robustness: 0.3, + policy_violations: 0, + timestamp: 0.0, + }); + + let segment = cost_curve_to_segment(&curve, 3); + let decoded = cost_curve_from_segment(&segment).unwrap(); + + assert_eq!(decoded.domain_id, DomainId("test".into())); + assert_eq!(decoded.points.len(), 1); + } + + #[test] + fn wrong_segment_type_detected() { + let kernel = PolicyKernel::new("k".into()); + let segment = policy_kernel_to_segment(&kernel, 1); + let result = transfer_prior_from_segment(&segment); + assert!(matches!(result, Err(RvfBridgeError::WrongSegmentType { .. }))); + } + + #[test] + fn witness_hash_is_deterministic() { + let prior = TransferPrior::uniform(DomainId("test".into())); + let h1 = compute_transfer_witness_hash(&prior); + let h2 = compute_transfer_witness_hash(&prior); + assert_eq!(h1, h2); + assert_ne!(h1, [0u8; 32]); + } + + #[test] + fn witness_entries_chain() { + let prior = TransferPrior::uniform(DomainId("d1".into())); + let entries = build_transfer_witness_entries( + &prior, + &DomainId("d1".into()), + &DomainId("d2".into()), + 2.5, + 1_000_000_000, + ); + assert_eq!(entries.len(), 2); + assert_eq!(entries[0].witness_type, WITNESS_TRANSFER); + assert_eq!(entries[1].witness_type, WITNESS_CONVERGENCE); + + // Verify the chain is valid after linking + let chain_bytes = rvf_crypto::create_witness_chain(&entries); + let verified = rvf_crypto::verify_witness_chain(&chain_bytes).unwrap(); + assert_eq!(verified.len(), 2); + } + + #[test] + fn tlv_round_trip() { + let entries = vec![ + AgiTlvEntry { + tag: rvf_types::AGI_TAG_TRANSFER_PRIOR, + value: b"hello".to_vec(), + }, + AgiTlvEntry { + tag: rvf_types::AGI_TAG_POLICY_KERNEL, + value: b"world".to_vec(), + }, + ]; + + let encoded = encode_tlv_entries(&entries); + let decoded = decode_tlv_entries(&encoded).unwrap(); + + assert_eq!(decoded.len(), 2); + assert_eq!(decoded[0].tag, rvf_types::AGI_TAG_TRANSFER_PRIOR); + assert_eq!(decoded[0].value, b"hello"); + assert_eq!(decoded[1].tag, rvf_types::AGI_TAG_POLICY_KERNEL); + assert_eq!(decoded[1].value, b"world"); + } + + #[test] + fn agi_container_packaging() { + let prior = TransferPrior::uniform(DomainId("test".into())); + let kernel = PolicyKernel::new("k0".into()); + let scoreboard = crate::cost_curve::AccelerationScoreboard::new(); + + let entries = package_for_agi_container(&[prior], &[kernel], &scoreboard); + assert_eq!(entries.len(), 2); // 1 prior + 1 kernel, 0 curves + + let encoded = encode_tlv_entries(&entries); + let decoded = decode_tlv_entries(&encoded).unwrap(); + assert_eq!(decoded.len(), 2); + } + + #[test] + fn solver_prior_exchange_round_trip() { + let arms = vec!["greedy".into(), "exploratory".into()]; + let mut engine = MetaThompsonEngine::new(arms); + let domain = DomainId("test".into()); + engine.init_domain_uniform(domain.clone()); + + let bucket = ContextBucket { + difficulty_tier: "medium".into(), + category: "algorithm".into(), + }; + for _ in 0..20 { + engine.record_outcome( + &domain, + bucket.clone(), + crate::transfer::ArmId("greedy".into()), + 0.9, + 1.0, + ); + } + + let exchanges = extract_solver_priors(&engine, &domain); + assert!(!exchanges.is_empty()); + + // Import into a fresh engine + let new_arms = vec!["greedy".into(), "exploratory".into()]; + let mut new_engine = MetaThompsonEngine::new(new_arms); + let target = DomainId("target".into()); + new_engine.init_domain_uniform(target.clone()); + import_solver_priors(&mut new_engine, &target, &exchanges); + + // Should have transferred priors + let extracted = new_engine.extract_prior(&target); + assert!(extracted.is_some()); + } + + #[test] + fn multi_segment_assembly() { + let prior = TransferPrior::uniform(DomainId("d1".into())); + let kernel = PolicyKernel::new("k0".into()); + let mut curve = CostCurve::new( + DomainId("d1".into()), + ConvergenceThresholds::default(), + ); + curve.record(CostCurvePoint { + cycle: 0, + accuracy: 0.5, + cost_per_solve: 0.05, + robustness: 0.5, + policy_violations: 0, + timestamp: 0.0, + }); + + let assembled = assemble_domain_expansion_segments( + &[prior], + &[kernel], + &[curve], + 100, + ); + + // Should contain 3 segments, each 64-byte aligned + assert!(assembled.len() >= 3 * 64); + assert_eq!(assembled.len() % 64, 0); + + // Verify first segment header magic + let magic = u32::from_le_bytes([ + assembled[0], + assembled[1], + assembled[2], + assembled[3], + ]); + assert_eq!(magic, rvf_types::SEGMENT_MAGIC); + } +} diff --git a/crates/rvf/rvf-types/src/agi_container.rs b/crates/rvf/rvf-types/src/agi_container.rs index b6005b74..e595d0a9 100644 --- a/crates/rvf/rvf-types/src/agi_container.rs +++ b/crates/rvf/rvf-types/src/agi_container.rs @@ -45,6 +45,8 @@ pub const AGI_OFFLINE_CAPABLE: u16 = 1 << 9; pub const AGI_HAS_TOOLS: u16 = 1 << 10; /// Container includes coherence gate configuration. pub const AGI_HAS_COHERENCE_GATES: u16 = 1 << 11; +/// Container includes cross-domain transfer learning data. +pub const AGI_HAS_DOMAIN_EXPANSION: u16 = 1 << 12; // --- TLV tags for the manifest payload --- @@ -84,6 +86,14 @@ pub const AGI_TAG_DEPENDENCY_SNAPSHOT: u16 = 0x010F; pub const AGI_TAG_AUTHORITY_CONFIG: u16 = 0x0110; /// Target domain profile identifier. pub const AGI_TAG_DOMAIN_PROFILE: u16 = 0x0111; +/// Cross-domain transfer prior (posterior summaries). +pub const AGI_TAG_TRANSFER_PRIOR: u16 = 0x0112; +/// Policy kernel configuration and performance history. +pub const AGI_TAG_POLICY_KERNEL: u16 = 0x0113; +/// Cost curve convergence and acceleration data. +pub const AGI_TAG_COST_CURVE: u16 = 0x0114; +/// Counterexample archive (failed solutions for future decisions). +pub const AGI_TAG_COUNTEREXAMPLES: u16 = 0x0115; // --- Execution mode --- @@ -398,6 +408,11 @@ impl AgiContainerHeader { self.flags & AGI_HAS_COHERENCE_GATES != 0 } + /// Check if the container has domain expansion data. + pub const fn has_domain_expansion(&self) -> bool { + self.flags & AGI_HAS_DOMAIN_EXPANSION != 0 + } + /// Serialize header to a 64-byte array. pub fn to_bytes(&self) -> [u8; AGI_HEADER_SIZE] { let mut buf = [0u8; AGI_HEADER_SIZE]; @@ -479,6 +494,8 @@ pub struct ContainerSegments { pub orchestrator_present: bool, /// World model data present (VEC + INDEX segments). pub world_model_present: bool, + /// Domain expansion (transfer priors, policy kernels, cost curves) present. + pub domain_expansion_present: bool, /// Total container size in bytes. pub total_size: u64, } @@ -552,6 +569,9 @@ impl ContainerSegments { { flags |= AGI_HAS_WORLD_MODEL; } + if self.domain_expansion_present { + flags |= AGI_HAS_DOMAIN_EXPANSION; + } flags } } diff --git a/crates/rvf/rvf-types/src/lib.rs b/crates/rvf/rvf-types/src/lib.rs index 9e48d045..77ee2353 100644 --- a/crates/rvf/rvf-types/src/lib.rs +++ b/crates/rvf/rvf-types/src/lib.rs @@ -126,5 +126,8 @@ pub use agi_container::{ AGI_HAS_KERNEL, AGI_HAS_WASM, AGI_HAS_ORCHESTRATOR, AGI_HAS_WORLD_MODEL, AGI_HAS_EVAL, AGI_HAS_SKILLS, AGI_HAS_WITNESS, AGI_SIGNED, AGI_REPLAY_CAPABLE, AGI_OFFLINE_CAPABLE, AGI_HAS_TOOLS, AGI_HAS_COHERENCE_GATES, + AGI_HAS_DOMAIN_EXPANSION, AGI_TAG_AUTHORITY_CONFIG, AGI_TAG_DOMAIN_PROFILE, + AGI_TAG_TRANSFER_PRIOR, AGI_TAG_POLICY_KERNEL, + AGI_TAG_COST_CURVE, AGI_TAG_COUNTEREXAMPLES, }; diff --git a/crates/rvf/rvf-types/src/segment_type.rs b/crates/rvf/rvf-types/src/segment_type.rs index 39d58cad..5a69c972 100644 --- a/crates/rvf/rvf-types/src/segment_type.rs +++ b/crates/rvf/rvf-types/src/segment_type.rs @@ -56,6 +56,12 @@ pub enum SegmentType { Membership = 0x22, /// Sparse delta patches. Delta = 0x23, + /// Serialized transfer prior (cross-domain posterior summaries + cost EMAs). + TransferPrior = 0x30, + /// Policy kernel configuration and performance history. + PolicyKernel = 0x31, + /// Cost curve convergence data for acceleration tracking. + CostCurve = 0x32, } impl TryFrom for SegmentType { @@ -84,6 +90,9 @@ impl TryFrom for SegmentType { 0x21 => Ok(Self::Refcount), 0x22 => Ok(Self::Membership), 0x23 => Ok(Self::Delta), + 0x30 => Ok(Self::TransferPrior), + 0x31 => Ok(Self::PolicyKernel), + 0x32 => Ok(Self::CostCurve), other => Err(other), } } @@ -117,6 +126,9 @@ mod tests { SegmentType::Refcount, SegmentType::Membership, SegmentType::Delta, + SegmentType::TransferPrior, + SegmentType::PolicyKernel, + SegmentType::CostCurve, ]; for v in variants { let raw = v as u8; @@ -127,10 +139,18 @@ mod tests { #[test] fn invalid_value_returns_err() { assert_eq!(SegmentType::try_from(0x24), Err(0x24)); + assert_eq!(SegmentType::try_from(0x33), Err(0x33)); assert_eq!(SegmentType::try_from(0xF0), Err(0xF0)); assert_eq!(SegmentType::try_from(0xFF), Err(0xFF)); } + #[test] + fn domain_expansion_discriminants() { + assert_eq!(SegmentType::TransferPrior as u8, 0x30); + assert_eq!(SegmentType::PolicyKernel as u8, 0x31); + assert_eq!(SegmentType::CostCurve as u8, 0x32); + } + #[test] fn kernel_ebpf_wasm_discriminants() { assert_eq!(SegmentType::Kernel as u8, 0x0E);