action-policy
Source: crates/lvcs-action/src/policy.rs.
/// A counted set of attestations of one frontier.
///
/// They all name `frontier`, `toolchain_hash`, and `actions`, and come from
/// the distinct runner keys `runners`, at most one per runner key
/// and one per device. `runners.len()` is the set's size, which
/// `channel.read` reports.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Counted {
/// The frontier every attestation names.
pub frontier: FrontierId,
/// The toolchain hash every attestation names.
pub toolchain_hash: [u8; 32],
/// The actions, with their inputs, every attestation names.
pub actions: BTreeMap<ActionId, InputsHash>,
/// The runner keys whose attestations count.
pub runners: BTreeSet<ParticipantKey>,
}
/// The largest counted set among `attestations` (of one frontier, from one
/// channel), when it reaches `threshold`.
///
/// It holds at most one attestation per runner key and at most one per
/// device, all naming the same frontier id, `toolchain_hash`, and `actions`
/// map, and that map holding every action in `required` (every change
/// action but the generator at `lvcs.toml`'s approved heads, decisions 127
/// and 245). An
/// attestation whose frontier holds an override or workspace document
/// counts toward nothing. `None` when no set reaches `threshold`. Equal
/// attestations count once, so concurrent puts of one entry never count
/// twice. Among sets of the largest size, the one whose frontier id, then
/// `toolchain_hash`, then `actions` sorts first is chosen; its `runners`
/// are a maximum matching of runner keys to devices, found in key
/// order, so every peer reports the same set for the same attestations.
#[must_use]
pub fn counted(
attestations: &[Attestation],
required: &BTreeSet<ActionId>,
threshold: u32,
) -> Option<Counted> {
largest(attestations, required).filter(|set| reaches(set, threshold))
}
/// What one derivation's records for one `InputsHash` come to (row 180).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum GenerationState {
/// At least the threshold of runners' records agree on every
/// output digest, and no record differs or failed.
Counted,
/// Two records name different output digests: never counts.
Diverged,
/// A record failed: never counts.
Failed,
}
/// The state of one `(derive, inputs)`'s records, and the record that
/// stands for it.
///
/// For `Counted`, `record` is the least runner key's record (the
/// records all agree on every output digest; their heads may differ). For
/// `Failed`, it is the least runner key's failed record. For
/// `Diverged`, it is the least runner key's record among those whose
/// digests differ from the least runner key's record.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GenerationCount<'a> {
/// What the records come to.
pub state: GenerationState,
/// The record that stands for it.
pub record: &'a Generation,
}
/// What `records`, every record of one derivation for one `InputsHash`,
/// come to under `threshold` (row 180, decision 120's rule).
///
/// A failed record makes them `Failed`, and two records whose output
/// digests differ make them `Diverged`; neither ever counts, so a later
/// contradicting record un-counts them. Otherwise they are `Counted` when
/// a maximum matching of their runner keys to their devices (at most
/// one record per key and one per device, as [`counted`] has it) reaches
/// `threshold`. `None` when there is no record, or the agreeing records
/// don't reach `threshold`. Records are ordered by runner key, then
/// device, then signature, so every peer picks the same record for the
/// same records.
#[must_use]
pub fn generation(records: &[Generation], threshold: u32) -> Option<GenerationCount<'_>> {
let mut ordered: Vec<&Generation> = records.iter().collect();
ordered.sort_by_key(|record| record_order(record));
let first = *ordered.first()?;
if let Some(failed) = ordered.iter().find(|record| record.failure.is_some()) {
return Some(GenerationCount {
state: GenerationState::Failed,
record: failed,
});
}
let agreed = digests(first);
if let Some(differing) = ordered.iter().find(|record| digests(record) != agreed) {
return Some(GenerationCount {
state: GenerationState::Diverged,
record: differing,
});
}
let pairs: BTreeSet<(ParticipantKey, DeviceKey)> = ordered
.iter()
.map(|record| (record.runner, record.device))
.collect();
let runners = u32::try_from(matching(&pairs).len()).unwrap_or(u32::MAX);
(runners >= threshold).then_some(GenerationCount {
state: GenerationState::Counted,
record: first,
})
}