crtx-verifier 0.1.1

Pure independent-witness reducer for trusted release/compliance evidence (ADR 0041).
Documentation
//! Shared test helpers for `cortex-verifier` integration tests.
//!
//! Builds deterministic test witnesses backed by in-memory Ed25519 keys.
//! Per ADR 0041 §"Pure trust path", the verifier never resolves a key from
//! disk; these helpers mirror that by handing a freshly generated keypair
//! to each witness at construction time.

// Cargo's integration-test model compiles this module once per test binary;
// unused helpers in any one binary trigger `dead_code` warnings even when the
// other binaries do use them. Suppress at the module boundary.
#![allow(dead_code)]

use chrono::{DateTime, Utc};
use cortex_core::{AuthorityClass, ClaimCeiling, ClaimProofState, RuntimeMode};
use cortex_verifier::{
    AuthorityDomain, EvidenceInput, EvidenceKind, IndependentWitness, WitnessClass, WitnessPayload,
    WitnessSignature, WitnessTier,
};
use ed25519_dalek::{Signer, SigningKey};

/// Deterministic 32-byte seed -> signing key. Tests pass distinct seeds to
/// keep witness identities disjoint without relying on RNG.
pub fn signing_key(seed: u8) -> SigningKey {
    let mut bytes = [0u8; 32];
    bytes[0] = seed;
    bytes[31] = seed.wrapping_add(0x5A);
    SigningKey::from_bytes(&bytes)
}

/// Sample evidence digest (hex BLAKE3 of the producer-supplied evidence input
/// bytes). The actual byte content is irrelevant for the verifier's trust
/// decision; it only checks subject binding against this string.
pub fn sample_evidence_digest() -> String {
    blake3::hash(b"release readiness evidence v1")
        .to_hex()
        .to_string()
}

/// Build an `EvidenceInput` shaped like a real `cortex release readiness`
/// authority-grade input that the CLI already parses today.
pub fn authority_grade_input(kind: EvidenceKind) -> EvidenceInput {
    EvidenceInput {
        kind,
        evidence_blake3: sample_evidence_digest(),
        runtime_mode: RuntimeMode::AuthorityGrade,
        authority_class: AuthorityClass::Operator,
        proof_state: ClaimProofState::FullChainVerified,
        requested_ceiling: ClaimCeiling::AuthorityGrade,
        source_refs: vec!["signed://fixture/evidence".to_string()],
        advisory_only: false,
    }
}

/// Build an `EvidenceInput` flagged advisory-only (dev / pre-v2 paths).
pub fn advisory_only_input(kind: EvidenceKind) -> EvidenceInput {
    EvidenceInput {
        kind,
        evidence_blake3: sample_evidence_digest(),
        runtime_mode: RuntimeMode::Dev,
        authority_class: AuthorityClass::Observed,
        proof_state: ClaimProofState::Partial,
        requested_ceiling: ClaimCeiling::AuthorityGrade,
        source_refs: vec!["local://fixture/evidence".to_string()],
        advisory_only: true,
    }
}

/// Construct a signed witness over the supplied subject digest at the supplied
/// `asserted_at` instant. The class drives both the `authority_domain` and
/// the typed payload — overriding `authority_domain` separately is supported
/// via [`set_authority_domain`].
pub fn signed_witness(
    seed: u8,
    class: WitnessClass,
    tier: WitnessTier,
    subject_digest: &str,
    asserted_at: DateTime<Utc>,
) -> IndependentWitness {
    let key = signing_key(seed);
    let domain = class.required_authority_domain();
    let payload = sample_payload(class);
    let preimage = payload.canonical_preimage(domain, subject_digest, asserted_at);
    let signature = key.sign(&preimage);
    IndependentWitness {
        class,
        authority_domain: domain,
        tier,
        asserted_at,
        asserted_subject_blake3: subject_digest.to_string(),
        signature: WitnessSignature::Ed25519 {
            public_key_bytes: key.verifying_key().to_bytes(),
            signature_bytes: signature.to_bytes(),
            signer_id: Some(format!("test-signer-{}", class.wire_str())),
        },
        payload,
    }
}

/// Sample payload for each witness class. Values are deterministic; they
/// only need to be consistent with what the witness's preimage covers.
pub fn sample_payload(class: WitnessClass) -> WitnessPayload {
    let now =
        DateTime::<Utc>::from_timestamp(1_715_500_000, 0).expect("constant fits in chrono range");
    match class {
        WitnessClass::SignedLedgerChainHead => WitnessPayload::SignedLedgerChainHead {
            chain_head_hash: blake3::hash(b"chain-head").to_hex().to_string(),
            event_count: 42,
        },
        WitnessClass::ExternalAnchorCrossing => WitnessPayload::ExternalAnchorCrossing {
            chain_head_hash: blake3::hash(b"chain-head").to_hex().to_string(),
            event_count: 42,
            sink_kind: "branch_protection".to_string(),
        },
        WitnessClass::RemoteCiConclusion => WitnessPayload::RemoteCiConclusion {
            workflow_run_id: "ci-run-2026-05-12".to_string(),
            commit_sha: "0000000000000000000000000000000000000000".to_string(),
            conclusion_timestamp: now,
        },
        WitnessClass::ReproducibleBuildProvenance => WitnessPayload::ReproducibleBuildProvenance {
            builder_id: "https://slsa-builder.example/v1".to_string(),
            source_digest: blake3::hash(b"source").to_hex().to_string(),
            artifact_digest: blake3::hash(b"artifact").to_hex().to_string(),
        },
    }
}

/// Override the witness's authority_domain in place; useful to test the
/// authority-overlap invariant. Re-signs the canonical preimage with the
/// existing signing key seed so the signature stays valid. Only works with
/// the `Ed25519` variant; panics if the witness carries a different signature
/// type (which test helpers never produce).
pub fn force_authority_domain(witness: &mut IndependentWitness, seed: u8, domain: AuthorityDomain) {
    witness.authority_domain = domain;
    let key = signing_key(seed);
    let preimage = witness.payload.canonical_preimage(
        witness.authority_domain,
        &witness.asserted_subject_blake3,
        witness.asserted_at,
    );
    let signature = key.sign(&preimage);
    match &mut witness.signature {
        WitnessSignature::Ed25519 {
            public_key_bytes,
            signature_bytes,
            ..
        } => {
            *public_key_bytes = key.verifying_key().to_bytes();
            *signature_bytes = signature.to_bytes();
        }
        other => panic!("force_authority_domain: expected Ed25519, got {other:?}"),
    }
}

/// Build the canonical set of four witnesses that satisfy
/// `FullChainVerified` for the supplied evidence kind at the supplied
/// reference time.
pub fn full_witness_set(
    subject_digest: &str,
    asserted_at: DateTime<Utc>,
) -> Vec<IndependentWitness> {
    vec![
        signed_witness(
            1,
            WitnessClass::SignedLedgerChainHead,
            WitnessTier::OperatorOwned,
            subject_digest,
            asserted_at,
        ),
        signed_witness(
            2,
            WitnessClass::ExternalAnchorCrossing,
            WitnessTier::OperatorOwned,
            subject_digest,
            asserted_at,
        ),
        signed_witness(
            3,
            WitnessClass::RemoteCiConclusion,
            WitnessTier::ThirdParty,
            subject_digest,
            asserted_at,
        ),
        signed_witness(
            4,
            WitnessClass::ReproducibleBuildProvenance,
            WitnessTier::ThirdParty,
            subject_digest,
            asserted_at,
        ),
    ]
}

/// `EvidenceKind` re-export so tests don't import the module twice.
pub use cortex_verifier::EvidenceKind as Kind;