Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
222
crates/ruvector-verified/src/pipeline.rs
Normal file
222
crates/ruvector-verified/src/pipeline.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
//! Verified pipeline composition.
|
||||
//!
|
||||
//! Provides `VerifiedStage` for type-safe pipeline stages and `compose_stages`
|
||||
//! for proving that two stages can be composed (output type matches input type).
|
||||
|
||||
use crate::error::{Result, VerificationError};
|
||||
use crate::ProofEnvironment;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A verified pipeline stage with proven input/output type compatibility.
|
||||
///
|
||||
/// `A` and `B` are phantom type parameters representing the stage's
|
||||
/// logical input and output types (compile-time markers, not runtime).
|
||||
///
|
||||
/// The `proof_id` field references the proof term that the stage's
|
||||
/// implementation correctly transforms `A` to `B`.
|
||||
#[derive(Debug)]
|
||||
pub struct VerifiedStage<A, B> {
|
||||
/// Human-readable stage name (e.g., "kmer_embedding", "variant_call").
|
||||
pub name: String,
|
||||
/// Proof term ID.
|
||||
pub proof_id: u32,
|
||||
/// Input type term ID in the environment.
|
||||
pub input_type_id: u32,
|
||||
/// Output type term ID in the environment.
|
||||
pub output_type_id: u32,
|
||||
_phantom: PhantomData<(A, B)>,
|
||||
}
|
||||
|
||||
impl<A, B> VerifiedStage<A, B> {
|
||||
/// Create a new verified stage with its correctness proof.
|
||||
pub fn new(
|
||||
name: impl Into<String>,
|
||||
proof_id: u32,
|
||||
input_type_id: u32,
|
||||
output_type_id: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
proof_id,
|
||||
input_type_id,
|
||||
output_type_id,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the stage name.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
/// Compose two verified stages, producing a proof that the pipeline is type-safe.
|
||||
///
|
||||
/// Checks that `f.output_type_id == g.input_type_id` (pointer equality via
|
||||
/// hash-consing). If they match, constructs a composed stage `A -> C`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `TypeCheckFailed` if the output type of `f` does not match
|
||||
/// the input type of `g`.
|
||||
pub fn compose_stages<A, B, C>(
|
||||
f: &VerifiedStage<A, B>,
|
||||
g: &VerifiedStage<B, C>,
|
||||
env: &mut ProofEnvironment,
|
||||
) -> Result<VerifiedStage<A, C>> {
|
||||
// Verify output(f) = input(g) via ID equality (hash-consed)
|
||||
if f.output_type_id != g.input_type_id {
|
||||
return Err(VerificationError::TypeCheckFailed(format!(
|
||||
"pipeline type mismatch: stage '{}' output (type#{}) != stage '{}' input (type#{})",
|
||||
f.name, f.output_type_id, g.name, g.input_type_id,
|
||||
)));
|
||||
}
|
||||
|
||||
// Construct composed proof
|
||||
let proof_id = env.alloc_term();
|
||||
env.stats.proofs_verified += 1;
|
||||
|
||||
Ok(VerifiedStage::new(
|
||||
format!("{} >> {}", f.name, g.name),
|
||||
proof_id,
|
||||
f.input_type_id,
|
||||
g.output_type_id,
|
||||
))
|
||||
}
|
||||
|
||||
/// Compose a chain of stages, verifying each connection.
|
||||
///
|
||||
/// Takes a list of (name, input_type_id, output_type_id) and produces
|
||||
/// a single composed stage spanning the entire chain.
|
||||
pub fn compose_chain(
|
||||
stages: &[(String, u32, u32)],
|
||||
env: &mut ProofEnvironment,
|
||||
) -> Result<(u32, u32, u32)> {
|
||||
if stages.is_empty() {
|
||||
return Err(VerificationError::ProofConstructionFailed(
|
||||
"empty pipeline chain".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut current_output = stages[0].2;
|
||||
let mut proof_ids = Vec::with_capacity(stages.len());
|
||||
proof_ids.push(env.alloc_term());
|
||||
|
||||
for (i, stage) in stages.iter().enumerate().skip(1) {
|
||||
if current_output != stage.1 {
|
||||
return Err(VerificationError::TypeCheckFailed(format!(
|
||||
"chain break at stage {}: type#{} != type#{}",
|
||||
i, current_output, stage.1,
|
||||
)));
|
||||
}
|
||||
proof_ids.push(env.alloc_term());
|
||||
current_output = stage.2;
|
||||
}
|
||||
|
||||
env.stats.proofs_verified += stages.len() as u64;
|
||||
let final_proof = env.alloc_term();
|
||||
Ok((stages[0].1, current_output, final_proof))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Marker types for phantom parameters
|
||||
#[derive(Debug)]
|
||||
struct KmerInput;
|
||||
#[derive(Debug)]
|
||||
struct EmbeddingOutput;
|
||||
#[derive(Debug)]
|
||||
struct AlignmentOutput;
|
||||
#[derive(Debug)]
|
||||
struct VariantOutput;
|
||||
|
||||
#[test]
|
||||
fn test_verified_stage_creation() {
|
||||
let stage: VerifiedStage<KmerInput, EmbeddingOutput> =
|
||||
VerifiedStage::new("kmer_embed", 0, 1, 2);
|
||||
assert_eq!(stage.name(), "kmer_embed");
|
||||
assert_eq!(stage.input_type_id, 1);
|
||||
assert_eq!(stage.output_type_id, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_stages_matching() {
|
||||
let mut env = ProofEnvironment::new();
|
||||
|
||||
let f: VerifiedStage<KmerInput, EmbeddingOutput> = VerifiedStage::new("embed", 0, 1, 2);
|
||||
let g: VerifiedStage<EmbeddingOutput, AlignmentOutput> =
|
||||
VerifiedStage::new("align", 1, 2, 3);
|
||||
|
||||
let composed = compose_stages(&f, &g, &mut env);
|
||||
assert!(composed.is_ok());
|
||||
let c = composed.unwrap();
|
||||
assert_eq!(c.name(), "embed >> align");
|
||||
assert_eq!(c.input_type_id, 1);
|
||||
assert_eq!(c.output_type_id, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_stages_mismatch() {
|
||||
let mut env = ProofEnvironment::new();
|
||||
|
||||
let f: VerifiedStage<KmerInput, EmbeddingOutput> = VerifiedStage::new("embed", 0, 1, 2);
|
||||
let g: VerifiedStage<EmbeddingOutput, AlignmentOutput> =
|
||||
VerifiedStage::new("align", 1, 99, 3); // 99 != 2
|
||||
|
||||
let composed = compose_stages(&f, &g, &mut env);
|
||||
assert!(composed.is_err());
|
||||
let err = composed.unwrap_err();
|
||||
assert!(matches!(err, VerificationError::TypeCheckFailed(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_three_stages() {
|
||||
let mut env = ProofEnvironment::new();
|
||||
|
||||
let f: VerifiedStage<KmerInput, EmbeddingOutput> = VerifiedStage::new("embed", 0, 1, 2);
|
||||
let g: VerifiedStage<EmbeddingOutput, AlignmentOutput> =
|
||||
VerifiedStage::new("align", 1, 2, 3);
|
||||
let h: VerifiedStage<AlignmentOutput, VariantOutput> = VerifiedStage::new("call", 2, 3, 4);
|
||||
|
||||
let fg = compose_stages(&f, &g, &mut env).unwrap();
|
||||
let fgh = compose_stages(&fg, &h, &mut env).unwrap();
|
||||
assert_eq!(fgh.name(), "embed >> align >> call");
|
||||
assert_eq!(fgh.input_type_id, 1);
|
||||
assert_eq!(fgh.output_type_id, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_chain() {
|
||||
let mut env = ProofEnvironment::new();
|
||||
let stages = vec![
|
||||
("embed".into(), 1u32, 2u32),
|
||||
("align".into(), 2, 3),
|
||||
("call".into(), 3, 4),
|
||||
];
|
||||
let result = compose_chain(&stages, &mut env);
|
||||
assert!(result.is_ok());
|
||||
let (input, output, _proof) = result.unwrap();
|
||||
assert_eq!(input, 1);
|
||||
assert_eq!(output, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_chain_break() {
|
||||
let mut env = ProofEnvironment::new();
|
||||
let stages = vec![
|
||||
("embed".into(), 1u32, 2u32),
|
||||
("align".into(), 99, 3), // break: 99 != 2
|
||||
];
|
||||
let result = compose_chain(&stages, &mut env);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compose_chain_empty() {
|
||||
let mut env = ProofEnvironment::new();
|
||||
let result = compose_chain(&[], &mut env);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user