Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/labcolors-core/src/selection_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl SelectionCandidateKeyV1 {
Self(bytes)
}

fn as_bytes(&self) -> &[u8] {
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.0
}
}
Expand Down
112 changes: 112 additions & 0 deletions crates/labcolors-core/src/selection_release_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,115 @@ fn bijective_payload_relabeling_preserves_the_order_structure() {
.collect::<Vec<_>>();
assert_eq!(relabeled.as_ref(), expected.as_slice());
}

/// The exhaustive selection oracle (V5c-3).
///
/// Deliberately shares no code path with `select_order_v1`: it reads only the
/// authored tie groups, finds each candidate's rank by linear scan, and orders
/// by a quadratic selection sort over exhaustive pairwise dominance. It must
/// agree with production materialisation on every binding permutation.
fn exhaustive_select<C: Copy>(
groups: &[&[&[u8]]],
candidates: &[(C, SelectionCandidateKeyV1)],
) -> Box<[C]> {
let authored_rank = |candidate: &SelectionCandidateKeyV1| -> usize {
for (rank, group) in groups.iter().enumerate() {
if group.iter().any(|bytes| *bytes == candidate.as_bytes()) {
return rank;
}
}
unreachable!("every oracle candidate is ranked by the authored release");
};
let dominates = |left: &(C, &SelectionCandidateKeyV1),
right: &(C, &SelectionCandidateKeyV1)| {
let left_rank = authored_rank(left.1);
let right_rank = authored_rank(right.1);
left_rank < right_rank || left_rank == right_rank && left.1.as_bytes() < right.1.as_bytes()
};
let mut remaining = candidates
.iter()
.map(|(payload, key)| (*payload, key))
.collect::<Vec<_>>();
let mut ordered = Vec::with_capacity(remaining.len());
while !remaining.is_empty() {
let mut chosen = 0;
for index in 1..remaining.len() {
if dominates(&remaining[index], &remaining[chosen]) {
chosen = index;
}
}
ordered.push(remaining.swap_remove(chosen).0);
}
ordered.into_boxed_slice()
}

#[test]
fn exhaustive_oracle_agrees_with_production_materialisation() {
let groups: &[&[&[u8]]] = &[&[b"zz", b"aa"], &[b"mm"], &[b"bb", b"cc"]];
let admitted =
admit_selection_release_v1(release(1, groups)).expect("authored release must admit");
let candidates = [
(5u32, key(b"cc")),
(4, key(b"mm")),
(3, key(b"aa")),
(2, key(b"bb")),
(1, key(b"zz")),
];
let production = admitted.select_order_v1(&candidates).unwrap();
assert_eq!(
production.as_ref(),
exhaustive_select(groups, &candidates).as_ref()
);
}

#[test]
fn exhaustive_oracle_matches_production_over_every_binding_permutation() {
let groups: &[&[&[u8]]] = &[&[b"alpha", b"zeta"], &[b"beta"]];
let admitted =
admit_selection_release_v1(release(1, groups)).expect("authored release must admit");
let base = [(0u32, key(b"alpha")), (1, key(b"zeta")), (2, key(b"beta"))];
let permutation_table: &[&[usize]] = &[
&[0, 1, 2],
&[0, 2, 1],
&[1, 0, 2],
&[1, 2, 0],
&[2, 0, 1],
&[2, 1, 0],
];
for permutation in permutation_table {
let permuted = permutation
.iter()
.map(|index| base[*index].clone())
.collect::<Vec<_>>();
let production = admitted.select_order_v1(&permuted).unwrap();
assert_eq!(
production.as_ref(),
exhaustive_select(groups, &permuted).as_ref(),
"oracle and production must agree under binding permutation {permutation:?}"
);
assert_eq!(production.as_ref(), &[0u32, 1, 2]);
}
}

#[test]
fn exhaustive_oracle_is_not_vacuous() {
let groups: &[&[&[u8]]] = &[&[b"aa", b"zz"], &[b"mm"]];
let candidates = [(1u32, key(b"zz")), (2, key(b"aa")), (3, key(b"mm"))];
// the declaration order of the candidates is not the authored order
assert_ne!(
exhaustive_select(groups, &candidates).as_ref(),
[1u32, 2, 3]
);
// the tie inside the authored group breaks on key bytes, never on payload
// or declaration position
assert_eq!(
exhaustive_select(groups, &candidates).as_ref(),
[2u32, 1, 3]
);
// a release with a different policy produces a different exhaustive order
let reversed: &[&[&[u8]]] = &[&[b"mm"], &[b"aa", b"zz"]];
assert_eq!(
exhaustive_select(reversed, &candidates).as_ref(),
[3u32, 2, 1]
);
}
Loading