use prikk_object::{
BlockKind, CanonicalEncode, ObjectEnvelope, ObjectId, ObjectType, RecognitionClaimPayload,
};
use super::{
ContradictedField, RecognitionClaimConsistency, check_recognition_claim_consistency,
order_claims_for_sealing,
};
use crate::test_support::{maintainer_signature, signed_block, unique_temp_dir};
use crate::trust::load_maintainer_trust_policy;
use crate::{FileObjectStore, ObjectWriter, RepositoryLayout};
fn write_claim(
objects: &mut FileObjectStore,
block_id: ObjectId,
patch_ids: Vec<ObjectId>,
parent_block_ids: Vec<ObjectId>,
) -> prikk_error::Result<ObjectId> {
let payload = RecognitionClaimPayload {
block_id,
patch_ids,
parent_block_ids,
};
let mut envelope = ObjectEnvelope::unsigned(
ObjectType::RecognitionClaim,
1,
payload.to_canonical_bytes()?,
);
envelope.add_signature(maintainer_signature())?;
objects.write_object(&envelope)
}
#[test]
fn claim_about_an_absent_block_reads_block_absent() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc115-recognition-claim-absent");
let layout = RepositoryLayout::init(root.clone())?;
let store = FileObjectStore::new(layout);
let claim = RecognitionClaimPayload {
block_id: ObjectId::from_bytes([0x71; 32]),
patch_ids: vec![ObjectId::from_bytes([0x72; 32])],
parent_block_ids: Vec::new(),
};
let outcome = check_recognition_claim_consistency(&store, &claim)?;
assert_eq!(outcome, RecognitionClaimConsistency::BlockAbsent);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn claim_contradicting_a_held_block_is_contradicted() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc115-recognition-claim-contradicted");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let real_patch = ObjectId::from_bytes([0x73; 32]);
let block = signed_block(BlockKind::Normal, Vec::new(), vec![real_patch], None);
let block_id = store.write_object(&block)?;
let lying_patch = ObjectId::from_bytes([0x74; 32]);
let claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![lying_patch],
parent_block_ids: Vec::new(),
};
let outcome = check_recognition_claim_consistency(&store, &claim)?;
assert_eq!(
outcome,
RecognitionClaimConsistency::Contradicted {
field: ContradictedField::PatchIds,
claimed: vec![lying_patch],
actual: vec![real_patch],
}
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn claim_carrying_the_blocks_own_verbatim_order_round_trips_and_reads_consistent()
-> prikk_error::Result<()> {
let root = unique_temp_dir("rfc115-recognition-claim-verbatim-round-trip");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let low = ObjectId::from_bytes([0x75; 32]);
let high = ObjectId::from_bytes([0x76; 32]);
let block = signed_block(BlockKind::Normal, Vec::new(), vec![high, low], None);
let block_id = store.write_object(&block)?;
let claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![high, low],
parent_block_ids: Vec::new(),
};
let bytes = claim.to_canonical_bytes()?;
let decoded = RecognitionClaimPayload::decode_canonical(&bytes)?;
assert_eq!(
decoded.patch_ids,
vec![high, low],
"order must survive the round trip unchanged, not be re-sorted"
);
let outcome = check_recognition_claim_consistency(&store, &decoded)?;
assert_eq!(outcome, RecognitionClaimConsistency::Consistent);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn claim_permuting_a_held_blocks_own_order_is_contradicted() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc115-recognition-claim-permuted-contradicted");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let low = ObjectId::from_bytes([0x75; 32]);
let high = ObjectId::from_bytes([0x76; 32]);
let block = signed_block(BlockKind::Normal, Vec::new(), vec![high, low], None);
let block_id = store.write_object(&block)?;
let claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![low, high],
parent_block_ids: Vec::new(),
};
let outcome = check_recognition_claim_consistency(&store, &claim)?;
assert_eq!(
outcome,
RecognitionClaimConsistency::Contradicted {
field: ContradictedField::PatchIds,
claimed: vec![low, high],
actual: vec![high, low],
}
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn checking_a_recognition_claim_never_changes_the_adopted_key_set() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc115-recognition-claim-trust-inert");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout.clone());
crate::trust::add_trusted_maintainer(
&layout,
"rfc115-trust-inert-maintainer",
"1111111111111111111111111111111111111111111111111111111111111111",
)?;
let patch = ObjectId::from_bytes([0x77; 32]);
let block = signed_block(BlockKind::Normal, Vec::new(), vec![patch], None);
let block_id = store.write_object(&block)?;
let before = load_maintainer_trust_policy(&layout)?;
let absent_claim = RecognitionClaimPayload {
block_id: ObjectId::from_bytes([0x78; 32]),
patch_ids: vec![patch],
parent_block_ids: Vec::new(),
};
check_recognition_claim_consistency(&store, &absent_claim)?;
assert_eq!(load_maintainer_trust_policy(&layout)?, before);
let contradicted_claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![ObjectId::from_bytes([0x79; 32])],
parent_block_ids: Vec::new(),
};
check_recognition_claim_consistency(&store, &contradicted_claim)?;
assert_eq!(load_maintainer_trust_policy(&layout)?, before);
let consistent_claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![patch],
parent_block_ids: Vec::new(),
};
check_recognition_claim_consistency(&store, &consistent_claim)?;
assert_eq!(load_maintainer_trust_policy(&layout)?, before);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn claim_with_wrong_parents_is_contradicted_naming_the_parent_field() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc116-recognition-claim-parent-contradicted");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let real_parent = ObjectId::from_bytes([0x81; 32]);
let patch = ObjectId::from_bytes([0x82; 32]);
let block = signed_block(BlockKind::Normal, vec![real_parent], vec![patch], None);
let block_id = store.write_object(&block)?;
let lying_parent = ObjectId::from_bytes([0x83; 32]);
let claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![patch],
parent_block_ids: vec![lying_parent],
};
let outcome = check_recognition_claim_consistency(&store, &claim)?;
assert_eq!(
outcome,
RecognitionClaimConsistency::Contradicted {
field: ContradictedField::ParentBlockIds,
claimed: vec![lying_parent],
actual: vec![real_parent],
}
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn claim_with_wrong_patches_still_reports_as_a_patch_mismatch() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc116-recognition-claim-patch-mismatch-with-parents");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let real_parent = ObjectId::from_bytes([0x84; 32]);
let real_patch = ObjectId::from_bytes([0x85; 32]);
let block = signed_block(BlockKind::Normal, vec![real_parent], vec![real_patch], None);
let block_id = store.write_object(&block)?;
let lying_patch = ObjectId::from_bytes([0x86; 32]);
let claim = RecognitionClaimPayload {
block_id,
patch_ids: vec![lying_patch],
parent_block_ids: vec![real_parent],
};
let outcome = check_recognition_claim_consistency(&store, &claim)?;
assert_eq!(
outcome,
RecognitionClaimConsistency::Contradicted {
field: ContradictedField::PatchIds,
claimed: vec![lying_patch],
actual: vec![real_patch],
}
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn order_claims_row1_two_block_batch_seals_parent_first_regardless_of_input_order()
-> prikk_error::Result<()> {
let root = unique_temp_dir("rfc116-order-row1");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let patch = ObjectId::from_bytes([0x01; 32]);
let parent_block = ObjectId::from_bytes([0x10; 32]);
let child_block = ObjectId::from_bytes([0x20; 32]);
let parent_claim = write_claim(&mut store, parent_block, vec![patch], Vec::new())?;
let child_claim = write_claim(&mut store, child_block, vec![patch], vec![parent_block])?;
let order = order_claims_for_sealing(&store, &[child_claim, parent_claim])?;
assert_eq!(order, vec![parent_claim, child_claim]);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn order_claims_row2_ordering_is_by_parent_block_ids_not_artifact_or_id_order()
-> prikk_error::Result<()> {
let root = unique_temp_dir("rfc116-order-row2");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let patch = ObjectId::from_bytes([0x01; 32]);
let parent_block = ObjectId::from_bytes([0xF0; 32]);
let child_block = ObjectId::from_bytes([0x00; 32]);
assert!(
parent_block > child_block,
"fixture must put the parent's own block id after the child's"
);
let parent_claim = write_claim(&mut store, parent_block, vec![patch], Vec::new())?;
let child_claim = write_claim(&mut store, child_block, vec![patch], vec![parent_block])?;
assert!(
parent_claim > child_claim,
"fixture must also produce a parent claim id that sorts after the child claim id -- \
confirmed here, not assumed, or this test cannot distinguish a real topological sort \
from sorting by claim id: parent={parent_claim} child={child_claim}"
);
let order = order_claims_for_sealing(&store, &[parent_claim, child_claim])?;
assert_eq!(
order,
vec![parent_claim, child_claim],
"the parent's claim must still come first, even though both the block ids and the claim \
ids themselves sort the other way"
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn order_claims_row3_parents_outside_the_batch_are_ignored_not_refused() -> prikk_error::Result<()>
{
let root = unique_temp_dir("rfc116-order-row3");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let patch = ObjectId::from_bytes([0x01; 32]);
let already_sealed_elsewhere = ObjectId::from_bytes([0xAB; 32]); let block = ObjectId::from_bytes([0x30; 32]);
let claim = write_claim(
&mut store,
block,
vec![patch],
vec![already_sealed_elsewhere],
)?;
let order = order_claims_for_sealing(&store, &[claim])?;
assert_eq!(order, vec![claim]);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn order_claims_row4_a_hostile_cycle_is_refused_naming_both_claims() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc116-order-row4");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let patch = ObjectId::from_bytes([0x01; 32]);
let block_b = ObjectId::from_bytes([0x40; 32]);
let block_c = ObjectId::from_bytes([0x50; 32]);
let claim_p = write_claim(&mut store, block_b, vec![patch], vec![block_c])?;
let claim_q = write_claim(&mut store, block_c, vec![patch], vec![block_b])?;
let result = order_claims_for_sealing(&store, &[claim_p, claim_q]);
let message = match result {
Ok(order) => panic!("a hostile cycle must be refused, got an order: {order:?}"),
Err(error) => error.to_string(),
};
assert!(
message.contains(&claim_p.to_string()) && message.contains(&claim_q.to_string()),
"the refusal must name both claims in the cycle: {message}"
);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn order_claims_row5_the_order_is_deterministic_across_runs() -> prikk_error::Result<()> {
let root = unique_temp_dir("rfc116-order-row5");
let layout = RepositoryLayout::init(root.clone())?;
let mut store = FileObjectStore::new(layout);
let patch = ObjectId::from_bytes([0x01; 32]);
let chain_a_root = write_claim(
&mut store,
ObjectId::from_bytes([0x61; 32]),
vec![patch],
Vec::new(),
)?;
let chain_a_leaf = write_claim(
&mut store,
ObjectId::from_bytes([0x62; 32]),
vec![patch],
vec![ObjectId::from_bytes([0x61; 32])],
)?;
let chain_b_root = write_claim(
&mut store,
ObjectId::from_bytes([0x71; 32]),
vec![patch],
Vec::new(),
)?;
let chain_b_leaf = write_claim(
&mut store,
ObjectId::from_bytes([0x72; 32]),
vec![patch],
vec![ObjectId::from_bytes([0x71; 32])],
)?;
let batch = [chain_b_leaf, chain_a_leaf, chain_b_root, chain_a_root];
let first = order_claims_for_sealing(&store, &batch)?;
for _ in 0..4 {
let repeat = order_claims_for_sealing(&store, &batch)?;
assert_eq!(
repeat, first,
"repeated runs over the identical batch must agree"
);
}
let _ = std::fs::remove_dir_all(root);
Ok(())
}