trazaeo 0.5.3

Open-source provenance SDK and specification for verifiable EO and climate data workflows
Documentation
use super::{
    make_attestation, verify_attestation, ArtifactRecord, Attestation, CaptureEnvelope,
    PublishEnvelope, TransformEnvelope,
};
use crate::checkpoint::CheckpointArtifact;

fn attestation() -> Attestation {
    Attestation {
        signer_id: "signer-1".to_string(),
        key_id: "key-1".to_string(),
        signature: "sig".to_string(),
        signed_at: "2026-01-01T00:00:00Z".to_string(),
    }
}

fn capture() -> CaptureEnvelope {
    CaptureEnvelope {
        schema_version: "1.0.0".to_string(),
        envelope_type: "capture".to_string(),
        issued_at: "2026-01-01T00:00:00Z".to_string(),
        subject_id: "capture-1".to_string(),
        capture_role: "transport".to_string(),
        capture_actor_id: "station-a".to_string(),
        capture_system_id: "rx-1".to_string(),
        capture_window: "2026-01-01T00:00:00Z/2026-01-01T00:05:00Z".to_string(),
        segment_ids: vec!["seg-1".to_string()],
        input_refs: vec![],
        output_refs: vec!["obj://raw/1".to_string()],
        segment_hashes: vec!["hash-1".to_string()],
        rolling_hash_state: Some("rolling-1".to_string()),
        content_root_hash: "root-1".to_string(),
        content_commitment_profile: "blake3_root_v1".to_string(),
        chunk_size: 3,
        leaf_count: 1,
        content_descriptor_ref: None,
        content_descriptor_hash: None,
        attestations: vec![attestation()],
        key_id: "key-1".to_string(),
        policy_profile_id: None,
    }
}

fn transform() -> TransformEnvelope {
    TransformEnvelope {
        schema_version: "1.0.0".to_string(),
        envelope_type: "transform".to_string(),
        issued_at: "2026-01-01T00:00:00Z".to_string(),
        subject_id: "transform-1".to_string(),
        transform_job_id: "job-1".to_string(),
        transform_stage: "nc_to_zarr".to_string(),
        input_refs: vec!["obj://raw/1".to_string()],
        output_refs: vec!["obj://zarr/1".to_string()],
        input_artifact_roots: vec!["root-in".to_string()],
        output_artifact_roots: vec!["root-out".to_string()],
        input_artifacts: vec![ArtifactRecord {
            artifact_id: "input-1".to_string(),
            artifact_ref: "obj://raw/1".to_string(),
            content_root_hash: "root-in".to_string(),
        }],
        output_artifacts: vec![ArtifactRecord {
            artifact_id: "output-1".to_string(),
            artifact_ref: "obj://zarr/1".to_string(),
            content_root_hash: "root-out".to_string(),
        }],
        toolchain: "rust".to_string(),
        parameters_ref: "cfg://1".to_string(),
        parameters_hash: "cfg-hash".to_string(),
        determinism_profile: "det-v1".to_string(),
        runtime_env_ref: Some("oci://img".to_string()),
        runtime_env_hash: Some("img-hash".to_string()),
        provenance_start_mode: "transport_capture".to_string(),
        source_manifest_ref: None,
        source_manifest_hash: None,
        source_root_hash: None,
        transform_spec_ref: Some("spec://1".to_string()),
        transform_spec_hash: Some("spec-hash".to_string()),
        chunking_profile_ref: Some("chunk://1".to_string()),
        chunking_profile_hash: Some("chunk-hash".to_string()),
        execution_manifest_ref: Some("exec://1".to_string()),
        execution_manifest_hash: Some("exec-hash".to_string()),
        runtime_manifest_ref: Some("runtime://1".to_string()),
        runtime_manifest_hash: Some("runtime-hash".to_string()),
        attestations: vec![attestation()],
        key_id: "key-1".to_string(),
    }
}

fn publish() -> PublishEnvelope {
    PublishEnvelope {
        schema_version: "1.0.0".to_string(),
        envelope_type: "publish".to_string(),
        issued_at: "2026-01-01T00:00:00Z".to_string(),
        subject_id: "publish-1".to_string(),
        dataset_id: "sst".to_string(),
        dataset_version: "v1".to_string(),
        input_refs: vec!["obj://zarr/1".to_string()],
        output_refs: vec!["obj://release/1".to_string()],
        published_artifacts: vec![CheckpointArtifact {
            artifact_id: "artifact-1".to_string(),
            content_root_hash: "root-1".to_string(),
            content_descriptor_ref: None,
            content_descriptor_hash: None,
            media_type: "application/vnd+zarr".to_string(),
        }],
        primary_artifact_id: "artifact-1".to_string(),
        checkpoint_manifest_ref: "checkpoint://1".to_string(),
        checkpoint_manifest_hash: "checkpoint-hash".to_string(),
        checkpoint_id: "checkpoint-1".to_string(),
        checkpoint_log_root_hash: "checkpoint-log-root".to_string(),
        lineage_refs: vec!["capture://1".to_string(), "transform://1".to_string()],
        verification_policy_id: "verify-default".to_string(),
        attestations: vec![attestation()],
        key_id: "key-1".to_string(),
        stac_refs: vec![],
        reward_context_ref: None,
        reward_context_hash: None,
        provenance_start_mode: "transport_capture".to_string(),
        bootstrap_origin_label: None,
        reward_eligible: false,
    }
}

#[test]
fn capture_envelope_validates() {
    assert!(capture().validate().is_ok());
}

#[test]
fn capture_envelope_rejects_unknown_capture_role() {
    let mut envelope = capture();
    envelope.capture_role = "unknown".to_string();
    let errors = envelope
        .validate()
        .expect_err("unknown capture role must fail validation");
    assert!(errors.iter().any(|e| e.contains("capture_role")));
}

#[test]
fn transform_envelope_validates() {
    assert!(transform().validate().is_ok());
}

#[test]
fn dataset_bootstrap_transform_requires_spec_and_source_manifest_fields() {
    let mut env = transform();
    env.provenance_start_mode = "dataset_bootstrap".to_string();
    env.source_manifest_ref = None;
    env.source_manifest_hash = None;
    env.source_root_hash = None;
    env.transform_spec_ref = None;
    env.transform_spec_hash = None;

    let errors = env
        .validate()
        .expect_err("bootstrap transform must require spec and source");
    assert!(errors.iter().any(|e| e.contains("source_manifest_ref")));
    assert!(errors.iter().any(|e| e.contains("source_manifest_hash")));
    assert!(errors.iter().any(|e| e.contains("source_root_hash")));
    assert!(errors.iter().any(|e| e.contains("transform_spec_ref")));
    assert!(errors.iter().any(|e| e.contains("transform_spec_hash")));
}

#[test]
fn dataset_incremental_transform_requires_spec_and_source_manifest_fields() {
    let mut env = transform();
    env.provenance_start_mode = "dataset_incremental".to_string();
    env.source_manifest_ref = None;
    env.source_manifest_hash = None;
    env.source_root_hash = None;
    env.transform_spec_ref = None;
    env.transform_spec_hash = None;

    let errors = env
        .validate()
        .expect_err("incremental transform must require spec and source");
    assert!(errors.iter().any(|e| e.contains("source_manifest_ref")));
    assert!(errors.iter().any(|e| e.contains("source_manifest_hash")));
    assert!(errors.iter().any(|e| e.contains("source_root_hash")));
    assert!(errors.iter().any(|e| e.contains("transform_spec_ref")));
    assert!(errors.iter().any(|e| e.contains("transform_spec_hash")));
}

#[test]
fn publish_envelope_validates() {
    assert!(publish().validate().is_ok());
}

#[test]
fn dataset_bootstrap_publish_requires_origin_label() {
    let mut env = publish();
    env.provenance_start_mode = "dataset_bootstrap".to_string();
    env.bootstrap_origin_label = None;

    let errors = env
        .validate()
        .expect_err("bootstrap publish must require origin label");
    assert!(errors.iter().any(|e| e.contains("bootstrap_origin_label")));
}

#[test]
fn make_attestation_uses_ed25519_signatures() {
    const TEST_SIGNING_KEY_HEX: &str =
        "4f3edf983ac636a65a842ce7c78d9aa706d3b113bce036f9a4f5762b76f70f18";
    let payload = publish().canonical_attestation_payload_bytes();
    let att = make_attestation(
        "signer-1",
        TEST_SIGNING_KEY_HEX,
        "2026-01-01T00:00:00Z",
        &payload,
    )
    .expect("build attestation");
    assert!(verify_attestation(&att, &payload));
}