ruvector-verified 0.1.1

Formal verification layer for RuVector: proof-carrying vector operations with sub-microsecond overhead using lean-agentic dependent types
Documentation
//! 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 std::marker::PhantomData;
use crate::error::{Result, VerificationError};
use crate::ProofEnvironment;

/// 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());
    }
}