prikk-store 0.16.0

Prikk storage crate scaffold.
Documentation
use prikk_object::{BlobPayload, CanonicalEncode, ObjectEnvelope, ObjectType, text_span_hash};

use crate::lifecycle_cache::ReplayDerivedLifecycleState;
use crate::memory_store::MemoryObjectStore;
use crate::node_lifecycle::{LiveNode, NodeContent};
use crate::object_store::ObjectWriter;

use super::*;

#[test]
fn store_backed_resolver_reads_live_baseline_text() {
    let old_text = b"alpha beta gamma";
    let mut store = MemoryObjectStore::new();
    let text_blob = write_blob(&mut store, BlobKind::Text, old_text);
    let baseline = baseline_with_file_node(NodeKind::TextFile, text_blob, old_text, MODE_REGULAR);
    let evidence = store_evidence(&store, baseline);
    let left = change_perm(1, node(1), MODE_REGULAR, MODE_EXECUTABLE);
    let right = edit_text(2, node(1), old_text, b"alpha BETA gamma");

    assert_eq!(
        classify_pair_with_text_resolver_result(
            evidence.baseline_state(),
            &evidence,
            &left,
            &right
        )
        .expect("classification succeeds"),
        PairClass::Independent
    );
    assert_eq!(evidence.baseline_block_id(), blob(0xb0));
    assert_eq!(evidence.lineage_horizon_id(), blob(0xa0));
}

#[test]
fn missing_sealed_baseline_text_blob_is_evidence_error() {
    let store = MemoryObjectStore::new();
    let missing_blob = blob(0x44);
    let baseline = baseline_with_file_node(
        NodeKind::TextFile,
        missing_blob,
        b"alpha beta",
        MODE_REGULAR,
    );
    let evidence = store_evidence(&store, baseline);
    let left = change_perm(1, node(1), MODE_REGULAR, MODE_EXECUTABLE);
    let right = edit_text(2, node(1), b"alpha beta", b"alpha BETA");

    assert_evidence_error(
        classify_pair_with_text_resolver_result(
            evidence.baseline_state(),
            &evidence,
            &left,
            &right,
        ),
        EvidenceScope::SealedBaselineRequired,
        EvidenceFact::BaselineText,
    );
}

#[test]
fn missing_sealed_candidate_blob_is_evidence_error_but_unsealed_candidate_is_unknown() {
    let store = MemoryObjectStore::new();
    let baseline = NodeLifecycleState::new();
    let evidence = store_evidence(&store, baseline);
    let missing_blob = blob(0x55);

    match evidence.blob_kind(EvidenceScope::SealedCandidateRequired, missing_blob) {
        Evidence::Missing { scope, fact, .. } => {
            assert_eq!(scope, EvidenceScope::SealedCandidateRequired);
            assert_eq!(fact, EvidenceFact::BlobKind);
        }
        other => panic!("expected sealed candidate missing evidence, got {other:?}"),
    }

    let left = create_file(1, "fresh.bin", node(1), missing_blob, MODE_REGULAR);
    let right = replace_binary(2, node(1), missing_blob, blob(0x66));
    assert_unknown(
        classify_pair_with_text_resolver_result(
            evidence.baseline_state(),
            &evidence,
            &left,
            &right,
        )
        .expect("missing unsealed candidate is a classification"),
        UnknownReason::MissingCandidateEvidence,
    );
}

#[test]
fn wrong_object_type_for_baseline_blob_is_evidence_error() {
    let mut store = MemoryObjectStore::new();
    let wrong_object = ObjectEnvelope::unsigned(ObjectType::Block, 1, b"not a blob".to_vec());
    let wrong_id = store
        .write_object(&wrong_object)
        .expect("write wrong object");
    let baseline =
        baseline_with_file_node(NodeKind::TextFile, wrong_id, b"alpha beta", MODE_REGULAR);
    let evidence = store_evidence(&store, baseline);
    let left = change_perm(1, node(1), MODE_REGULAR, MODE_EXECUTABLE);
    let right = edit_text(2, node(1), b"alpha beta", b"alpha BETA");

    match classify_pair_with_text_resolver_result(
        evidence.baseline_state(),
        &evidence,
        &left,
        &right,
    ) {
        Err(EvidenceError::WrongObjectType {
            scope,
            object_id,
            expected,
            actual,
        }) => {
            assert_eq!(scope, EvidenceScope::SealedBaselineRequired);
            assert_eq!(object_id, wrong_id);
            assert_eq!(expected, ObjectType::Blob);
            assert_eq!(actual, ObjectType::Block);
        }
        other => panic!("expected wrong object type error, got {other:?}"),
    }
}

#[test]
fn malformed_baseline_blob_payload_is_evidence_error() {
    let mut store = MemoryObjectStore::new();
    let malformed = ObjectEnvelope::unsigned(ObjectType::Blob, 1, b"bad blob".to_vec());
    let malformed_id = store
        .write_object(&malformed)
        .expect("write malformed blob");
    let baseline = baseline_with_file_node(
        NodeKind::TextFile,
        malformed_id,
        b"alpha beta",
        MODE_REGULAR,
    );
    let evidence = store_evidence(&store, baseline);
    let left = change_perm(1, node(1), MODE_REGULAR, MODE_EXECUTABLE);
    let right = edit_text(2, node(1), b"alpha beta", b"alpha BETA");

    match classify_pair_with_text_resolver_result(
        evidence.baseline_state(),
        &evidence,
        &left,
        &right,
    ) {
        Err(EvidenceError::Malformed { scope, fact, .. }) => {
            assert_eq!(scope, EvidenceScope::SealedBaselineRequired);
            assert_eq!(fact, EvidenceFact::BaselineText);
        }
        other => panic!("expected malformed evidence error, got {other:?}"),
    }
}

#[test]
fn wrong_blob_kind_for_baseline_text_is_evidence_error() {
    let mut store = MemoryObjectStore::new();
    let binary_blob = write_blob(&mut store, BlobKind::Binary, b"alpha beta");
    let baseline =
        baseline_with_file_node(NodeKind::TextFile, binary_blob, b"alpha beta", MODE_REGULAR);
    let evidence = store_evidence(&store, baseline);
    let left = change_perm(1, node(1), MODE_REGULAR, MODE_EXECUTABLE);
    let right = edit_text(2, node(1), b"alpha beta", b"alpha BETA");

    match classify_pair_with_text_resolver_result(
        evidence.baseline_state(),
        &evidence,
        &left,
        &right,
    ) {
        Err(EvidenceError::WrongBlobKind {
            scope,
            blob_id,
            expected,
            actual,
        }) => {
            assert_eq!(scope, EvidenceScope::SealedBaselineRequired);
            assert_eq!(blob_id, binary_blob);
            assert_eq!(expected, BlobKind::Text);
            assert_eq!(actual, BlobKind::Binary);
        }
        other => panic!("expected wrong blob kind error, got {other:?}"),
    }
}

#[test]
fn replay_unavailable_prevents_store_resolver_construction() {
    let store = MemoryObjectStore::new();

    match StorePatchAlgebraEvidence::from_store(&store, blob(0xb0), blob(0xa0)) {
        Err(EvidenceError::Unreadable {
            scope,
            fact,
            object_id,
            ..
        }) => {
            assert_eq!(scope, EvidenceScope::SealedBaselineRequired);
            assert_eq!(fact, EvidenceFact::BaselineState);
            assert_eq!(object_id, Some(blob(0xb0)));
        }
        other => panic!("expected replay construction evidence error, got {other:?}"),
    }
}

#[test]
fn future_precondition_reason_remains_reserved_for_unsupported_records() {
    assert_eq!(
        UnknownReason::FuturePreconditionDeferred,
        UnknownReason::FuturePreconditionDeferred
    );
}

fn store_evidence(
    store: &MemoryObjectStore,
    baseline: NodeLifecycleState,
) -> StorePatchAlgebraEvidence<'_, MemoryObjectStore> {
    let replay =
        ReplayDerivedLifecycleState::from_replay(blob(0xb0), baseline).expect("valid replay state");
    StorePatchAlgebraEvidence::from_replay_derived(store, blob(0xa0), replay)
        .expect("store evidence")
}

fn baseline_with_file_node(
    kind: NodeKind,
    blob_id: ObjectId,
    text_for_span: &[u8],
    mode: u32,
) -> NodeLifecycleState {
    let mut baseline = NodeLifecycleState::new();
    baseline
        .seed_live_node(
            node(1),
            LiveNode {
                path: path("note.txt"),
                kind,
                content: NodeContent::File { blob_id, mode },
            },
        )
        .expect("seed baseline");
    assert_eq!(text_span_hash(text_for_span), text_span_hash(text_for_span));
    baseline
}

fn write_blob(store: &mut MemoryObjectStore, kind: BlobKind, content: &[u8]) -> ObjectId {
    let payload = BlobPayload::new(kind, content.to_vec());
    let envelope = ObjectEnvelope::unsigned(
        ObjectType::Blob,
        1,
        payload.to_canonical_bytes().expect("encode blob"),
    );
    store.write_object(&envelope).expect("write blob")
}