feat(domain-expansion): integrate with RVF format — segment serialization, witness chains, AGI packaging

Connects the domain expansion engine to the RuVector Format (RVF) wire
protocol, closing all integration gaps:

- Add SegmentType::TransferPrior (0x30), PolicyKernel (0x31), CostCurve (0x32)
  to rvf-types for domain expansion segment packaging
- Add AGI_HAS_DOMAIN_EXPANSION flag and AGI_TAG_TRANSFER_PRIOR/POLICY_KERNEL/
  COST_CURVE/COUNTEREXAMPLES TLV tags to AGI container types
- Create rvf_bridge module (feature-gated behind "rvf") with:
  - RVF segment round-trip serialization for all three core types
  - SHAKE-256 witness chain integration via rvf-crypto
  - AGI container TLV packaging and encoding/decoding
  - SolverPriorExchange bridge for rvf-solver-wasm prior transfer
  - Multi-segment file assembly for standalone domain expansion archives
- Wire-format wrappers (WireTransferPrior, WirePolicyKernel) handle
  HashMap<ContextBucket, _> → Vec<(K,V)> conversion for JSON safety
- Add RVF export methods to WASM crate (WasmRvfBridge) for browser-side
  segment serialization, witness hashing, and solver prior exchange
- 59 tests pass with rvf feature, 49 without — feature gate clean

https://claude.ai/code/session_01RnwD4x5cbpB7FPvoyYQz8G
This commit is contained in:
Claude 2026-02-16 02:44:09 +00:00
parent 66ca5ff07b
commit bc2d92fb62
9 changed files with 952 additions and 0 deletions

3
Cargo.lock generated
View file

@ -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",

View file

@ -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"]

View file

@ -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<Vec<u8>, 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<String, JsValue> {
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<Vec<u8>, 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<Vec<u8>, 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<String, JsValue> {
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<Vec<u8>, JsValue> {
let priors: Vec<ruvector_domain_expansion::TransferPrior> =
serde_json::from_str(priors_json)
.map_err(|e| JsValue::from_str(&format!("priors parse error: {e}")))?;
let kernels: Vec<ruvector_domain_expansion::PolicyKernel> =
serde_json::from_str(kernels_json)
.map_err(|e| JsValue::from_str(&format!("kernels parse error: {e}")))?;
let curves: Vec<ruvector_domain_expansion::CostCurve> =
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<String, JsValue> {
// 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<String> = prior
.bucket_priors
.values()
.flat_map(|arms| arms.keys().map(|a| a.0.clone()))
.collect::<std::collections::HashSet<_>>()
.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}")))
}
}

View file

@ -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 }

View file

@ -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,

View file

@ -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<ContextBucket, _>
// 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<WireTransferPrior> 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<ArmId, BetaParams> =
arms.into_iter().collect();
bucket_priors.insert(bucket, arm_map);
}
let cost_ema_priors: std::collections::HashMap<ContextBucket, f32> =
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<String>,
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<WirePolicyKernel> 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<u8> {
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<TransferPrior, RvfBridgeError> {
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<u8> {
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<PolicyKernel, RvfBridgeError> {
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<u8> {
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<CostCurve, RvfBridgeError> {
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<rvf_crypto::WitnessEntry> {
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<u8>,
}
/// 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<AgiTlvEntry> {
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<u8> {
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<Vec<AgiTlvEntry>, 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<SolverPriorExchange> {
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<u8> {
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);
}
}

View file

@ -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
}
}

View file

@ -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,
};

View file

@ -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<u8> for SegmentType {
@ -84,6 +90,9 @@ impl TryFrom<u8> 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);