northroot-proof-engine 0.2.0

Core proof computation and validation library for PoSH (Proof of Shape) and PoX (Proof of Execution)
Documentation
//! Determinism tests for v0.2 engine
//!
//! These tests ensure that identical inputs produce identical outputs
//! across platforms and runs.

use northroot_proof_engine::{
    canonicalize_span_shape, compute_data_shape_hash, compute_method_shape_root,
    compute_span_commitment_hash, compute_span_shape_hash, compute_trace_set_root,
    normalize_timestamp, ArtifactRef, MethodRef, SpanCommitment, SpanShape, SpanStatus,
};

#[test]
fn test_timestamp_normalization_determinism() {
    // Same instant in different formats should produce same normalized timestamp
    let t1 = normalize_timestamp("2025-11-04T05:00:00-05:00").unwrap();
    let t2 = normalize_timestamp("2025-11-04T10:00:00Z").unwrap();
    assert_eq!(t1, t2);
    assert_eq!(t1, "2025-11-04T10:00:00.000Z");

    // Multiple calls should be identical
    let t3 = normalize_timestamp("2025-11-04T10:00:00.123456Z").unwrap();
    let t4 = normalize_timestamp("2025-11-04T10:00:00.123456Z").unwrap();
    assert_eq!(t3, t4);
    assert_eq!(t3, "2025-11-04T10:00:00.123Z");
}

#[test]
fn test_span_shape_hash_determinism() {
    let shape = SpanShape {
        tool_id: "io.read_csv".to_string(),
        tool_version: "1".to_string(),
        param_schema: serde_json::json!({"delimiter": {"type": "string", "default": ","}}),
        checker_ref: Some("chk:csv-valid".to_string()),
    };

    let hash1 = compute_span_shape_hash(&shape).unwrap();
    let hash2 = compute_span_shape_hash(&shape).unwrap();
    assert_eq!(hash1, hash2, "Span shape hash must be deterministic");
}

#[test]
fn test_method_shape_root_sorted_determinism() {
    let a = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
    let b = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();
    let c = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".to_string();

    // Different orders should produce same root
    let root1 = compute_method_shape_root(&[b.clone(), a.clone()]).unwrap();
    let root2 = compute_method_shape_root(&[a.clone(), b.clone()]).unwrap();
    assert_eq!(root1, root2, "Order must not matter");

    // Same order multiple times
    let root3 = compute_method_shape_root(&[a.clone(), b.clone(), c.clone()]).unwrap();
    let root4 = compute_method_shape_root(&[a.clone(), b.clone(), c.clone()]).unwrap();
    assert_eq!(root3, root4, "Must be deterministic");
}

#[test]
fn test_span_commitment_hash_determinism() {
    let commitment = SpanCommitment {
        schema_version: "proof.v2".to_string(),
        trace_id: "tr_01H1234567890".to_string(),
        span_id: "sp-0001".to_string(),
        method_ref: MethodRef {
            method_id: "com.test/method".to_string(),
            version: "1.0.0".to_string(),
            method_shape_root:
                "sha256:0000000000000000000000000000000000000000000000000000000000000000"
                    .to_string(),
            image_digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"
                .to_string(),
            method_fingerprint: "git:deadbeef".to_string(),
        },
        data_shape_hash: "sha256:1111111111111111111111111111111111111111111111111111111111111111"
            .to_string(),
        span_shape_hash: "sha256:2222222222222222222222222222222222222222222222222222222222222222"
            .to_string(),
        inputs: vec![ArtifactRef {
            name: "input.csv".to_string(),
            content_hash: "sha256:3333333333333333333333333333333333333333333333333333333333333333"
                .to_string(),
            uri: None,
        }],
        outputs: vec![ArtifactRef {
            name: "output.json".to_string(),
            content_hash: "sha256:4444444444444444444444444444444444444444444444444444444444444444"
                .to_string(),
            uri: None,
        }],
        start_time: "2025-11-06T12:00:00.000Z".to_string(),
        end_time: "2025-11-06T12:00:00.042Z".to_string(),
        status: SpanStatus::OK,
        meta: None,
    };

    let hash1 = compute_span_commitment_hash(&commitment).unwrap();
    let hash2 = compute_span_commitment_hash(&commitment).unwrap();
    assert_eq!(hash1, hash2, "Span commitment hash must be deterministic");
}

#[test]
fn test_trace_set_root_sorted_determinism() {
    let a = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
    let b = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();

    // Different orders should produce same root
    let root1 = compute_trace_set_root(&[b.clone(), a.clone()]).unwrap();
    let root2 = compute_trace_set_root(&[a.clone(), b.clone()]).unwrap();
    assert_eq!(root1, root2, "Order must not matter for trace_set_root");
}

#[test]
fn test_canonicalization_byte_for_byte() {
    let shape = SpanShape {
        tool_id: "io.read_csv".to_string(),
        tool_version: "1".to_string(),
        param_schema: serde_json::json!({"delimiter": {"type": "string"}}),
        checker_ref: None,
    };

    let canonical1 = canonicalize_span_shape(&shape).unwrap();
    let canonical2 = canonicalize_span_shape(&shape).unwrap();
    assert_eq!(
        canonical1, canonical2,
        "Canonicalization must be byte-for-byte identical"
    );
}

#[test]
fn test_data_shape_hash_determinism() {
    let schema = serde_json::json!({
        "type": "object",
        "properties": {
            "name": {"type": "string"}
        }
    });

    let hash1 = compute_data_shape_hash(&schema, None).unwrap();
    let hash2 = compute_data_shape_hash(&schema, None).unwrap();
    assert_eq!(hash1, hash2, "Data shape hash must be deterministic");
}