prikk-store 0.18.2

Prikk storage crate scaffold.
Documentation
use prikk_object::{ObjectEnvelope, ObjectId, ObjectType};

use super::{malformed_envelope, signature};
use crate::file_codec::encode_envelope_file_structural;
use crate::refs::encode_log_record_for_test;
use crate::test_support::{
    signed_empty_block_envelope, signed_patch_envelope, signed_ref_state_envelope,
    signed_ref_update_envelope, unique_temp_dir,
};
use crate::wal::encode_record_for_test;
use crate::{
    RepositoryLayout, SignatureEnvelopeSource, Wal, WalRecord, verify_repository,
    write_active_ref_metadata,
};

fn all_issues_envelope(object_type: ObjectType, payload: &[u8]) -> ObjectEnvelope {
    let duplicate = signature("a", 1);
    let mut malformed_middle = signature("b", 2);
    malformed_middle.signature_bytes.truncate(1);
    let mut repeated = duplicate.clone();
    repeated.created_at = 99;
    ObjectEnvelope {
        object_type,
        schema_version: 1,
        canonical_payload: payload.to_vec(),
        signatures: vec![duplicate, malformed_middle, repeated],
    }
}

fn write_legacy_object(
    layout: &RepositoryLayout,
    envelope: &ObjectEnvelope,
) -> prikk_error::Result<ObjectId> {
    let object_id = envelope.object_id();
    let path = layout.object_path(envelope.object_type, object_id);
    let parent = path
        .parent()
        .ok_or_else(|| prikk_error::PrikkError::Io("test object path has no parent".to_string()))?;
    std::fs::create_dir_all(parent)?;
    std::fs::write(path, encode_envelope_file_structural(envelope)?)?;
    Ok(object_id)
}

#[test]
fn format1_diagnostics_are_byte_preserving_suppressed_and_deterministic() -> prikk_error::Result<()>
{
    let root = unique_temp_dir("dc39-diagnostics");
    let layout = RepositoryLayout::init(root.clone())?;

    let patch_id = write_legacy_object(
        &layout,
        &malformed_envelope(ObjectType::Patch, b"legacy-patch", 63),
    )?;
    let blob_a = malformed_envelope(ObjectType::Blob, b"legacy-a", 65);
    let blob_a_id = write_legacy_object(&layout, &blob_a)?;
    let blob_b = all_issues_envelope(ObjectType::Blob, b"legacy-b");
    let blob_b_id = write_legacy_object(&layout, &blob_b)?;

    let mut wal_envelope = signed_patch_envelope();
    let wal_signature = wal_envelope.signatures.first_mut().ok_or_else(|| {
        prikk_error::PrikkError::Integrity("test WAL envelope is unsigned".to_string())
    })?;
    wal_signature.signature_bytes.truncate(1);
    let wal_record = WalRecord {
        seq: 1,
        envelope: wal_envelope.clone(),
    };
    std::fs::write(
        layout.default_queue_wal_path(),
        encode_record_for_test(&wal_record)?,
    )?;
    write_active_ref_metadata(&layout, "heads/main")?;

    let mut block = signed_empty_block_envelope();
    block.schema_version = 1;
    let block_id = write_legacy_object(&layout, &block)?;
    for ref_name in ["heads/z", "heads/a"] {
        let state = signed_ref_state_envelope(ref_name, None, block_id, 1);
        let state_id = write_legacy_object(&layout, &state)?;
        let update = signed_ref_update_envelope(ref_name, None, state_id, block_id, 1);
        let mut inverted_update = update;
        inverted_update.signatures = vec![signature("z", 9), signature("a", 8)];
        std::fs::write(
            layout.ref_log_path(ref_name),
            encode_log_record_for_test(&inverted_update)?,
        )?;
    }

    let observed_paths = [
        layout.object_path(ObjectType::Patch, patch_id),
        layout.object_path(ObjectType::Blob, blob_a_id),
        layout.object_path(ObjectType::Blob, blob_b_id),
        layout.default_queue_wal_path(),
        layout.ref_log_path("heads/a"),
        layout.ref_log_path("heads/z"),
    ];
    std::fs::write(root.join(".prikk/FORMAT"), b"1\n")?;
    let before = observed_paths
        .iter()
        .map(std::fs::read)
        .collect::<std::io::Result<Vec<_>>>()?;

    let legacy_layout = RepositoryLayout::open(root.clone())?;
    let report = verify_repository(&legacy_layout)?;
    let after = observed_paths
        .iter()
        .map(std::fs::read)
        .collect::<std::io::Result<Vec<_>>>()?;
    assert_eq!(before, after);

    let object_issues: Vec<_> = report
        .signature_envelope_issues
        .iter()
        .filter_map(|issue| match issue.source {
            SignatureEnvelopeSource::Object {
                object_type,
                object_id,
            } => Some((object_type.code(), object_id, issue.code)),
            _ => None,
        })
        .collect();
    assert_eq!(
        object_issues.first(),
        Some(&(
            ObjectType::Patch.code(),
            patch_id,
            "PRIKK-VERIFY-SIGNATURE-MALFORMED"
        ))
    );

    let mut blob_ids = [blob_a_id, blob_b_id];
    blob_ids.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
    let observed_blob_ids: Vec<_> = object_issues
        .iter()
        .filter(|(code, _, _)| *code == ObjectType::Blob.code())
        .map(|(_, object_id, _)| *object_id)
        .collect();
    assert_eq!(observed_blob_ids.first(), blob_ids.first());
    assert_eq!(observed_blob_ids.last(), blob_ids.last());

    let all_issue_codes: Vec<_> = report
        .signature_envelope_issues
        .iter()
        .filter(|issue| {
            matches!(
                issue.source,
                SignatureEnvelopeSource::Object { object_id, .. } if object_id == blob_b_id
            )
        })
        .map(|issue| issue.code)
        .collect();
    assert_eq!(
        all_issue_codes,
        [
            "PRIKK-VERIFY-SIGNATURE-MALFORMED",
            "PRIKK-VERIFY-SIGNATURE-DUPLICATE",
            "PRIKK-VERIFY-SIGNATURE-NONCANONICAL-ORDER",
        ]
    );

    let source_kinds: Vec<_> = report
        .signature_envelope_issues
        .iter()
        .map(|issue| match issue.source {
            SignatureEnvelopeSource::Object { .. } => 0,
            SignatureEnvelopeSource::ActiveWal { sequence: 1, .. } => 1,
            SignatureEnvelopeSource::RefLog { ref sequence, .. } if *sequence == 1 => 2,
            _ => 3,
        })
        .collect();
    assert!(
        source_kinds
            .windows(2)
            .all(|pair| matches!(pair, [left, right] if left <= right))
    );
    assert!(source_kinds.contains(&1));
    assert!(source_kinds.contains(&2));
    let ref_names: Vec<_> = report
        .signature_envelope_issues
        .iter()
        .filter_map(|issue| match &issue.source {
            SignatureEnvelopeSource::RefLog { ref_name, .. } => Some(ref_name.as_str()),
            _ => None,
        })
        .collect();
    assert_eq!(ref_names, ["heads/a", "heads/z"]);

    let _ = std::fs::remove_dir_all(root);
    Ok(())
}

#[test]
fn strict_writers_do_not_admit_legacy_diagnostic_envelopes() -> prikk_error::Result<()> {
    let root = unique_temp_dir("dc39-no-read-write-promotion");
    let layout = RepositoryLayout::init(root.clone())?;
    let malformed = malformed_envelope(ObjectType::Patch, b"legacy", 63);
    assert!(malformed.validate().is_ok());
    assert!(malformed.validate_strict().is_err());
    assert!(Wal::for_layout(&layout).append_patch(&malformed).is_err());

    let _ = std::fs::remove_dir_all(root);
    Ok(())
}