use ed25519_dalek::SigningKey; use ontoref_types::{ operation::{OpBody, OpPayload, Operation}, AttrId, EntityId, Hlc, PublicKey, Value, }; use ontoref_commit::{verify_witness, CommitLayer}; fn signing_key() -> SigningKey { SigningKey::from_bytes(&[11u8; 32]) } fn assert_op(entity: &str, attrs: Vec<(&str, Value)>, tx: Hlc) -> Operation { let key = signing_key(); let body = OpBody { actor: PublicKey::from_signing_key(&key), parents: vec![], payload: OpPayload::EntityAssert { attrs: attrs .into_iter() .map(|(name, value)| (AttrId::new(name), value)) .collect(), entity: EntityId::new(entity), valid_from: None, }, timestamp: tx, }; Operation::sign(body, &key) } fn fixture_ops() -> Vec { vec![ assert_op( "alice", vec![ ("name", Value::Str("Alice".into())), ("age", Value::Int(30)), ], Hlc::new(1, 0), ), assert_op( "bob", vec![ ("name", Value::Str("Bob".into())), ("age", Value::Int(25)), ], Hlc::new(2, 0), ), assert_op( "alice", vec![("age", Value::Int(31))], Hlc::new(3, 0), ), ] } #[test] fn determinism_same_ops_same_root() { let mut a = CommitLayer::new(); let mut b = CommitLayer::new(); for op in fixture_ops() { a.apply(&op); b.apply(&op); } assert_eq!(a.root(), b.root()); } #[test] fn determinism_empty_root_is_fixed() { let layer = CommitLayer::new(); let expected = *blake3::hash(b"empty-tree-v1").as_bytes(); assert_eq!(layer.root(), expected); } #[test] fn determinism_root_changes_with_value_overwrite() { let mut layer = CommitLayer::new(); layer.apply(&assert_op( "e", vec![("k", Value::Str("v1".into()))], Hlc::new(1, 0), )); let r1 = layer.root(); layer.apply(&assert_op( "e", vec![("k", Value::Str("v2".into()))], Hlc::new(2, 0), )); let r2 = layer.root(); assert_ne!(r1, r2, "root must change when an existing cell's value changes"); } #[test] fn witness_verify_succeeds_for_present_cell() { let mut layer = CommitLayer::new(); for op in fixture_ops() { layer.apply(&op); } let root = layer.root(); let witness = layer .witness(&EntityId::new("alice"), &AttrId::new("name")) .unwrap(); assert!(verify_witness(&witness, &root)); } #[test] fn witness_verify_succeeds_after_overwrite() { let mut layer = CommitLayer::new(); for op in fixture_ops() { layer.apply(&op); } let root = layer.root(); // alice's age was overwritten from 30 to 31; the witness should reflect 31. let witness = layer .witness(&EntityId::new("alice"), &AttrId::new("age")) .unwrap(); assert!(verify_witness(&witness, &root)); // The witnessed value should be the canonical encoding of Int(31). let expected = ontoref_types::canonical::encode(&Value::Int(31)).unwrap(); assert_eq!(witness.value, expected); } #[test] fn witness_verify_fails_for_tampered_value() { let mut layer = CommitLayer::new(); for op in fixture_ops() { layer.apply(&op); } let root = layer.root(); let mut witness = layer .witness(&EntityId::new("alice"), &AttrId::new("name")) .unwrap(); witness.value[0] ^= 0x01; assert!(!verify_witness(&witness, &root)); } #[test] fn witness_verify_fails_for_tampered_path() { let mut layer = CommitLayer::new(); for op in fixture_ops() { layer.apply(&op); } let root = layer.root(); let mut witness = layer .witness(&EntityId::new("alice"), &AttrId::new("name")) .unwrap(); if !witness.path.is_empty() { witness.path[0].sibling[0] ^= 0x01; } else { // For a 1-cell tree, witness.path is empty; force the tree larger so // the test exercises tampering at a real path step. panic!("fixture must produce a non-trivial Merkle path"); } assert!(!verify_witness(&witness, &root)); } #[test] fn witness_verify_fails_against_wrong_root() { let mut layer = CommitLayer::new(); for op in fixture_ops() { layer.apply(&op); } let witness = layer .witness(&EntityId::new("alice"), &AttrId::new("name")) .unwrap(); let bogus_root = [0xFFu8; 32]; assert!(!verify_witness(&witness, &bogus_root)); } #[test] fn cross_instance_determinism_with_independent_apply_order_per_entity() { // Two instances apply ops in the same logical order; their state roots // must be byte-identical. let ops = fixture_ops(); let mut left = CommitLayer::new(); for op in &ops { left.apply(op); } let mut right = CommitLayer::new(); for op in &ops { right.apply(op); } assert_eq!(left.root(), right.root()); assert_eq!(left.len(), right.len()); } #[test] fn cross_instance_witness_verifies_under_other_instance_root() { // A witness built on instance A verifies against instance B's root, // because both roots are identical. let ops = fixture_ops(); let mut left = CommitLayer::new(); let mut right = CommitLayer::new(); for op in &ops { left.apply(op); right.apply(op); } let witness = left .witness(&EntityId::new("bob"), &AttrId::new("name")) .unwrap(); let right_root = right.root(); assert!(verify_witness(&witness, &right_root)); } // ── ADR-041 / bl-029 29b: per-cell typed CRDT merge ────────────────────────── #[test] fn union_cell_accumulates_and_is_order_independent() { use ontoref_commit::{CellMergeKind, MergePolicy}; // The "tags" attribute folds as a grow-only set (GSet). let policy = MergePolicy::new().with("tags", CellMergeKind::Union); // Two actors assert different tag elements on the same cell. Applied in // either order, the cell must hold BOTH and hash to the same root. let a = assert_op("doc", vec![("tags", Value::Str("red".into()))], Hlc::new(1, 0)); let b = assert_op("doc", vec![("tags", Value::Str("blue".into()))], Hlc::new(2, 0)); let mut forward = CommitLayer::new().with_policy(policy.clone()); forward.apply(&a); forward.apply(&b); let mut reverse = CommitLayer::new().with_policy(policy.clone()); reverse.apply(&b); reverse.apply(&a); assert_eq!( forward.root(), reverse.root(), "union merge is order-independent — actors converge regardless of arrival order" ); // The union root differs from either single-element assertion: both // elements are retained, not last-writer-wins. let mut only_a = CommitLayer::new().with_policy(policy.clone()); only_a.apply(&a); assert_ne!( forward.root(), only_a.root(), "the second actor's element is retained, not overwritten" ); } #[test] fn lww_is_unchanged_without_a_policy() { // Default (empty) policy reproduces last-writer-wins: the later (higher // HLC, applied last) assertion wins the cell. let a = assert_op("e", vec![("v", Value::Int(1))], Hlc::new(1, 0)); let b = assert_op("e", vec![("v", Value::Int(2))], Hlc::new(2, 0)); let mut layer = CommitLayer::new(); layer.apply(&a); layer.apply(&b); // Equals a layer that only ever saw the winning (b) assertion. let mut only_b = CommitLayer::new(); only_b.apply(&b); assert_eq!(layer.root(), only_b.root(), "default policy stays last-writer-wins"); }