logicpearl-render 0.1.4

Rendering helpers for LogicPearl artifacts and verification status.
Documentation
use logicpearl_core::{ArtifactRenderer, Result};
use logicpearl_ir::{LogicPearlGateIr, RuleVerificationStatus};

pub struct TextInspector;

impl ArtifactRenderer<LogicPearlGateIr> for TextInspector {
    fn render(&self, gate: &LogicPearlGateIr) -> Result<String> {
        let mut lines = vec![
            format!("Gate ID: {}", gate.gate_id),
            format!("IR version: {}", gate.ir_version),
            format!("Features: {}", gate.input_schema.features.len()),
            format!("Rules: {}", gate.rules.len()),
        ];
        if let Some(verification) = &gate.verification {
            if let Some(scope) = &verification.correctness_scope {
                lines.push(format!("Correctness scope: {scope}"));
            }
        }
        let semantic_features = gate
            .input_schema
            .features
            .iter()
            .filter(|feature| feature.semantics.is_some())
            .count();
        if semantic_features > 0 {
            lines.push(format!("Feature dictionary entries: {semantic_features}"));
        }
        lines.push("Rule details:".to_string());
        for rule in &gate.rules {
            let status = match &rule.verification_status {
                Some(RuleVerificationStatus::SolverVerified) => "solver_verified",
                Some(RuleVerificationStatus::PipelineUnverified) => "pipeline_unverified",
                Some(RuleVerificationStatus::HeuristicUnverified) => "heuristic_unverified",
                Some(RuleVerificationStatus::RefinedUnverified) => "refined_unverified",
                None => "unknown",
            };
            lines.push(format!("  bit {}: {} [{}]", rule.bit, rule.id, status));
            if let Some(label) = &rule.label {
                lines.push(format!("    label: {label}"));
            }
            if let Some(message) = &rule.message {
                lines.push(format!("    message: {message}"));
            }
            if let Some(hint) = &rule.counterfactual_hint {
                lines.push(format!("    counterfactual: {hint}"));
            }
        }
        Ok(lines.join("\n"))
    }
}

#[cfg(test)]
mod tests {
    use super::TextInspector;
    use logicpearl_core::ArtifactRenderer;
    use logicpearl_ir::{
        ComparisonExpression, ComparisonOperator, ComparisonValue, EvaluationConfig, Expression,
        FeatureDefinition, FeatureType, InputSchema, LogicPearlGateIr, RuleDefinition, RuleKind,
        RuleVerificationStatus,
    };

    #[test]
    fn renders_backend_neutral_solver_verified_status() {
        let gate = LogicPearlGateIr {
            ir_version: "1.0".to_string(),
            gate_id: "demo_gate".to_string(),
            gate_type: "bitmask_gate".to_string(),
            input_schema: InputSchema {
                features: vec![FeatureDefinition {
                    id: "f_age".to_string(),
                    feature_type: FeatureType::Int,
                    description: Some("age".to_string()),
                    values: None,
                    min: None,
                    max: None,
                    editable: None,
                    semantics: None,
                    governance: None,
                    derived: None,
                }],
            },
            rules: vec![RuleDefinition {
                id: "rule_000".to_string(),
                kind: RuleKind::Predicate,
                bit: 0,
                deny_when: Expression::Comparison(ComparisonExpression {
                    feature: "f_age".to_string(),
                    op: ComparisonOperator::Lt,
                    value: ComparisonValue::FeatureRef {
                        feature_ref: "f_age".to_string(),
                    },
                }),
                label: None,
                message: None,
                severity: None,
                counterfactual_hint: None,
                verification_status: Some(RuleVerificationStatus::SolverVerified),
            }],
            evaluation: EvaluationConfig {
                combine: "bitwise_or".to_string(),
                allow_when_bitmask: 0,
            },
            verification: None,
            provenance: None,
        };

        let rendered = TextInspector
            .render(&gate)
            .expect("text inspector should render a simple gate");
        assert!(rendered.contains("solver_verified"));
        assert!(!rendered.contains("z3_verified"));
    }
}