vela-protocol 0.110.0

Core library for the Vela scientific knowledge protocol: replayable frontier state, signed canonical events, and proof packets.
Documentation
use serde_json::json;
use vela_protocol::bundle::{
    Assertion, Conditions, Confidence, ConfidenceKind, ConfidenceMethod, Evidence, Extraction,
    FindingBundle, Flags, Provenance,
};
use vela_protocol::events::{self, FindingEventInput, NULL_HASH};
use vela_protocol::project::{self, Project};
use vela_protocol::proposals::{
    ProofPacketRecord, StateProposal, new_proposal, record_proof_export,
};
use vela_protocol::{repo, state_integrity};

fn finding(id_text: &str) -> FindingBundle {
    let assertion = Assertion {
        text: format!("BBB integrity test finding {id_text}"),
        assertion_type: "mechanism".to_string(),
        entities: Vec::new(),
        relation: None,
        direction: None,
        causal_claim: None,
        causal_evidence_grade: None,
    };
    let provenance = Provenance {
        source_type: "published_paper".to_string(),
        doi: Some(format!("10.0000/integrity.{id_text}")),
        pmid: None,
        pmc: None,
        openalex_id: None,
        url: Some(format!("https://example.org/{id_text}")),
        title: format!("Integrity fixture {id_text}"),
        authors: Vec::new(),
        year: Some(2026),
        journal: None,
        license: None,
        publisher: None,
        funders: Vec::new(),
        extraction: Extraction::default(),
        review: None,
        citation_count: None,
    };
    let mut bundle = FindingBundle::new(
        assertion,
        Evidence {
            evidence_type: "experimental".to_string(),
            model_system: "human".to_string(),
            species: Some("Homo sapiens".to_string()),
            method: "manual".to_string(),
            sample_size: None,
            effect_size: None,
            p_value: None,
            replicated: false,
            replication_count: None,
            evidence_spans: Vec::new(),
        },
        Conditions {
            text: "human BBB context".to_string(),
            species_verified: vec!["Homo sapiens".to_string()],
            species_unverified: Vec::new(),
            in_vitro: false,
            in_vivo: false,
            human_data: true,
            clinical_trial: false,
            concentration_range: None,
            duration: None,
            age_group: None,
            cell_type: None,
        },
        Confidence {
            kind: ConfidenceKind::FrontierEpistemic,
            score: 0.5,
            basis: "fixture".to_string(),
            method: ConfidenceMethod::ExpertJudgment,
            components: None,
            extraction_confidence: 1.0,
        },
        provenance,
        Flags::default(),
    );
    bundle.created = "2026-05-07T00:00:00Z".to_string();
    bundle
}

fn frontier_with_one_finding() -> Project {
    let finding = finding("one");
    let mut frontier = project::assemble("integrity frontier", vec![finding.clone()], 0, 0, "test");
    frontier.frontier_id = Some("vfr_integrity_test".to_string());
    frontier
        .events
        .push(events::new_finding_event(FindingEventInput {
            kind: "finding.asserted",
            finding_id: &finding.id,
            actor_id: "reviewer:test",
            actor_type: "human",
            reason: "fixture genesis",
            before_hash: NULL_HASH,
            after_hash: &events::finding_hash(&finding),
            payload: json!({"proposal_id": "vpr_fixture", "finding": finding}),
            caveats: Vec::new(),
        }));
    frontier
}

#[test]
fn state_integrity_reports_duplicate_events_as_structural_failure() {
    let mut frontier = frontier_with_one_finding();
    frontier.events.push(frontier.events[0].clone());

    let report = state_integrity::analyze(&frontier);

    assert_eq!(report.schema, "vela.state_integrity_report.v0.1");
    assert_eq!(report.status, "fail");
    assert!(
        report
            .structural_errors
            .iter()
            .any(|error| error.rule_id == "duplicate_event_id")
    );
    assert_eq!(report.proof_freshness, "unknown");
}

#[test]
fn state_integrity_reports_applied_proposal_without_event() {
    let mut frontier = frontier_with_one_finding();
    let proposal = StateProposal {
        status: "applied".to_string(),
        applied_event_id: None,
        reviewed_by: Some("reviewer:test".to_string()),
        reviewed_at: Some("2026-05-07T00:00:00Z".to_string()),
        decision_reason: Some("fixture".to_string()),
        ..new_proposal(
            "finding.note",
            events::StateTarget {
                r#type: "finding".to_string(),
                id: frontier.findings[0].id.clone(),
            },
            "reviewer:test",
            "human",
            "fixture",
            json!({"text": "reviewed note"}),
            Vec::new(),
            Vec::new(),
        )
    };
    frontier.proposals.push(proposal);

    let report = state_integrity::analyze(&frontier);

    assert_eq!(report.status, "fail");
    assert!(
        report
            .structural_errors
            .iter()
            .any(|error| error.rule_id == "applied_proposal_missing_event")
    );
}

#[test]
fn state_integrity_reports_stale_proof_after_accepted_event() {
    let mut frontier = frontier_with_one_finding();
    let snapshot_hash = events::snapshot_hash(&frontier);
    let event_log_hash = events::event_log_hash(&frontier.events);
    record_proof_export(
        &mut frontier,
        ProofPacketRecord {
            generated_at: "2026-05-07T00:00:00Z".to_string(),
            snapshot_hash,
            event_log_hash,
            packet_manifest_hash:
                "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                    .to_string(),
        },
    );
    frontier
        .events
        .push(events::new_finding_event(FindingEventInput {
            kind: "finding.reviewed",
            finding_id: &frontier.findings[0].id,
            actor_id: "reviewer:test",
            actor_type: "human",
            reason: "new event after proof export",
            before_hash: &events::finding_hash(&frontier.findings[0]),
            after_hash: &events::finding_hash(&frontier.findings[0]),
            payload: json!({"proposal_id": "vpr_after_proof", "status": "accepted"}),
            caveats: Vec::new(),
        }));

    let report = state_integrity::analyze(&frontier);

    assert_eq!(report.status, "fail");
    assert_eq!(report.proof_freshness, "stale");
    assert!(
        report
            .structural_errors
            .iter()
            .any(|error| error.rule_id == "stale_proof_packet")
    );
}

#[test]
fn integrity_cli_json_reports_state_integrity() {
    let dir = tempfile::tempdir().expect("tempdir");
    let path = dir.path().join("frontier.json");
    let mut frontier = frontier_with_one_finding();
    frontier.events.push(frontier.events[0].clone());
    repo::save_to_path(&path, &frontier).expect("save frontier");

    let report = state_integrity::analyze_path(&path).expect("integrity report");

    assert_eq!(report.status, "fail");
    assert!(
        report
            .structural_errors
            .iter()
            .any(|error| error.rule_id == "duplicate_event_id")
    );
}