cordance-cortex 0.1.1

Cordance Cortex adapter. Emits candidate receipts; never writes to the cortex repo or runtime.
Documentation
use cordance_core::{
    advise::AdviseReport,
    lock::SourceLock,
    pack::{CordancePack, PackTargets, ProjectIdentity},
    schema,
    source::{SourceClass, SourceRecord},
};
use cordance_cortex::validator;

fn minimal_pack() -> CordancePack {
    CordancePack {
        schema: schema::CORDANCE_PACK_V1.into(),
        project: ProjectIdentity {
            name: "test".into(),
            repo_root: ".".into(),
            kind: "rust-workspace".into(),
            host_os: "linux".into(),
            axiom_pin: None,
        },
        sources: vec![],
        doctrine_pins: vec![],
        targets: PackTargets::default(),
        outputs: vec![],
        source_lock: SourceLock::empty(),
        advise: AdviseReport::empty(),
        residual_risk: vec!["claim_ceiling=candidate".into()],
    }
}

#[test]
fn build_receipt_authority_all_false() {
    let pack = minimal_pack();
    let receipt = cordance_cortex::build_receipt(&pack).expect("build must succeed");
    let ab = &receipt.authority_boundary;
    assert!(ab.candidate_only, "candidate_only must be true");
    assert!(!ab.cortex_truth_allowed);
    assert!(!ab.cortex_admission_allowed);
    assert!(!ab.durable_promotion_allowed);
    assert!(!ab.memory_promotion_allowed);
    assert!(!ab.doctrine_promotion_allowed);
    assert!(!ab.trusted_history_allowed);
    assert!(!ab.release_acceptance_allowed);
    assert!(!ab.runtime_authority_allowed);
}

#[test]
fn build_receipt_forbidden_uses_has_9_entries() {
    let pack = minimal_pack();
    let receipt = cordance_cortex::build_receipt(&pack).expect("build must succeed");
    assert_eq!(
        receipt.cordance_execution_receipt_v1.forbidden_uses.len(),
        9,
        "expected exactly 9 forbidden uses, got {:?}",
        receipt.cordance_execution_receipt_v1.forbidden_uses,
    );
}

#[test]
fn receipt_roundtrips_json() {
    let pack = minimal_pack();
    let receipt = cordance_cortex::build_receipt(&pack).expect("build must succeed");
    let json = serde_json::to_string(&receipt).expect("serialise");
    let back: cordance_core::receipt::CortexReceiptV1Candidate =
        serde_json::from_str(&json).expect("deserialise");
    assert_eq!(back.schema, schema::CORDANCE_CORTEX_RECEIPT_V1_CANDIDATE);
}

#[test]
fn tampered_authority_fails_validator() {
    let pack = minimal_pack();
    let receipt = cordance_cortex::build_receipt(&pack).expect("build must succeed");

    // Tamper via Value so the type system cannot catch it.
    let mut value: serde_json::Value = serde_json::to_value(&receipt).expect("to_value");
    value["authority_boundary"]["cortex_truth_allowed"] = serde_json::Value::Bool(true);

    let tampered: cordance_core::receipt::CortexReceiptV1Candidate =
        serde_json::from_value(value).expect("from_value");

    let result = validator::validate_receipt(&tampered);
    assert!(
        result.is_err(),
        "tampered receipt should fail validation but got Ok"
    );
}

#[test]
fn source_anchors_count_matches_non_blocked_sources() {
    let mut pack = minimal_pack();
    pack.sources = vec![
        SourceRecord {
            id: "project_readme:README.md".into(),
            path: "README.md".into(),
            class: SourceClass::ProjectReadme,
            sha256: "aabbccdd".repeat(8),
            size_bytes: 512,
            modified: None,
            blocked: false,
            blocked_reason: None,
        },
        SourceRecord {
            id: "project_source_code:src/main.rs".into(),
            path: "src/main.rs".into(),
            class: SourceClass::ProjectSourceCode,
            sha256: "11223344".repeat(8),
            size_bytes: 1024,
            modified: None,
            blocked: false,
            blocked_reason: None,
        },
        // This one is blocked and must NOT appear in the receipt.
        SourceRecord {
            id: "blocked_surface:secret.env".into(),
            path: "secret.env".into(),
            class: SourceClass::BlockedSurface,
            sha256: "deadbeef".repeat(8),
            size_bytes: 64,
            modified: None,
            blocked: true,
            blocked_reason: Some("matched blocked-surface rule".into()),
        },
    ];

    let receipt = cordance_cortex::build_receipt(&pack).expect("build must succeed");
    assert_eq!(
        receipt.cordance_execution_receipt_v1.source_anchors.len(),
        2,
        "only non-blocked sources should appear as anchors"
    );
}