radicle/cob/identity: Add sibling-accept invariant to property tests
This commit is contained in:
parent
a63f91885f
commit
2f1aaa45b2
|
|
@ -540,6 +540,7 @@ impl Harness {
|
||||||
self.assert_failed_parents_have_failed_children(identity);
|
self.assert_failed_parents_have_failed_children(identity);
|
||||||
self.assert_rejection_reasons_are_accurate(identity);
|
self.assert_rejection_reasons_are_accurate(identity);
|
||||||
self.assert_verdicts_match_parent_delegates(identity);
|
self.assert_verdicts_match_parent_delegates(identity);
|
||||||
|
self.assert_no_duplicate_sibling_accepts(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strong eventual consistency.
|
/// Strong eventual consistency.
|
||||||
|
|
@ -962,4 +963,60 @@ impl Harness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// No delegate has Accept verdicts on two Active siblings.
|
||||||
|
///
|
||||||
|
/// A delegate may hold at most one Accept across all Active children of
|
||||||
|
/// the same parent. If two siblings both accumulate majority accepts, the
|
||||||
|
/// `adopt()` function could encounter an already-rejected revision during
|
||||||
|
/// re-evaluation, which would violate internal assumptions.
|
||||||
|
///
|
||||||
|
/// PASS:
|
||||||
|
/// [Parent] → [Child A: Alice accepts] [Child B: Bob accepts]
|
||||||
|
///
|
||||||
|
/// FAIL:
|
||||||
|
/// [Parent] → [Child A: Alice accepts] [Child B: Alice accepts]
|
||||||
|
fn assert_no_duplicate_sibling_accepts(&self, identity: &Identity) {
|
||||||
|
for id in &identity.timeline {
|
||||||
|
let Some(rev) = identity.revision(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !rev.is_active() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(parent_id) = rev.parent else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (voter_key, verdict) in rev.verdicts() {
|
||||||
|
if !matches!(verdict, cob::identity::Verdict::Accept(_)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let voter_did = Did::from(*voter_key);
|
||||||
|
|
||||||
|
// Check that no Active sibling also has an accept from this voter.
|
||||||
|
for sibling_id in identity
|
||||||
|
.revision(&parent_id)
|
||||||
|
.map(|p| &p.children)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
if sibling_id == id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(sibling) = identity.revision(sibling_id)
|
||||||
|
&& sibling.is_active()
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
!sibling.accepted().any(|did| did == voter_did),
|
||||||
|
"Delegate {} has Accept verdicts on two active siblings: {} and {}",
|
||||||
|
voter_did,
|
||||||
|
id,
|
||||||
|
sibling_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue