use std::collections::HashSet;
use polyc_crypto::approval::{decode_admin_model_change, verify_admin_model_change};
use polyc_crypto::signing_role::{ApprovalRole, KeyStatus, RoleTrustSet};
use polyc_eventlog_model::Event;
use polyc_proto::kinds;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AdminSignatureStatus {
VerifiedCurrent,
VerifiedRetired,
Unverified,
Malformed,
}
impl AdminSignatureStatus {
pub const ALL: [Self; 4] = [
Self::VerifiedCurrent,
Self::VerifiedRetired,
Self::Unverified,
Self::Malformed,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::VerifiedCurrent => "verified_current",
Self::VerifiedRetired => "verified_retired",
Self::Unverified => "unverified",
Self::Malformed => "malformed",
}
}
}
impl std::fmt::Display for AdminSignatureStatus {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AdminModelChangeFact {
pub position: u64,
pub signature_status: AdminSignatureStatus,
pub principal: String,
pub previous_provider: String,
pub previous_model: String,
pub new_provider: String,
pub new_model: String,
pub changed_at_ms: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AdministratorAuditFacts {
pub model_changes: Vec<AdminModelChangeFact>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum AdministratorAuditError {
#[error("position {position} appears more than once in one source prefix")]
RepeatedPosition {
position: u64,
},
#[error("the administrator-audit partition carries an undeclared kind at position {position}")]
UndeclaredKind {
position: u64,
},
}
pub fn fold_administrator_audit(
events: &[(u64, Event)],
trust: &RoleTrustSet<ApprovalRole>,
) -> Result<AdministratorAuditFacts, AdministratorAuditError> {
refuse_repeated_positions(events)?;
let mut model_changes = Vec::with_capacity(events.len());
for (position, event) in events {
if event.kind != kinds::ADMIN_MODEL_CHANGE {
return Err(AdministratorAuditError::UndeclaredKind {
position: *position,
});
}
model_changes.push(fold_admin_model_change(*position, &event.payload, trust));
}
Ok(AdministratorAuditFacts { model_changes })
}
fn fold_admin_model_change(
position: u64,
payload: &[u8],
trust: &RoleTrustSet<ApprovalRole>,
) -> AdminModelChangeFact {
let Some(decoded) = decode_admin_model_change(payload) else {
return AdminModelChangeFact {
position,
signature_status: AdminSignatureStatus::Malformed,
principal: String::new(),
previous_provider: String::new(),
previous_model: String::new(),
new_provider: String::new(),
new_model: String::new(),
changed_at_ms: 0,
};
};
let signature_status = match verify_admin_model_change(payload) {
None => AdminSignatureStatus::Unverified,
Some(verified) => match trust.approval_key_status(&verified.signer_public_key) {
None => AdminSignatureStatus::Unverified,
Some(KeyStatus::Current) => AdminSignatureStatus::VerifiedCurrent,
Some(KeyStatus::Retired) => AdminSignatureStatus::VerifiedRetired,
},
};
AdminModelChangeFact {
position,
signature_status,
principal: decoded.principal,
previous_provider: decoded.previous_provider,
previous_model: decoded.previous_model,
new_provider: decoded.new_provider,
new_model: decoded.new_model,
changed_at_ms: decoded.changed_at_ms,
}
}
fn refuse_repeated_positions(events: &[(u64, Event)]) -> Result<(), AdministratorAuditError> {
let mut seen: HashSet<u64> = HashSet::with_capacity(events.len());
for (position, _) in events {
if !seen.insert(*position) {
return Err(AdministratorAuditError::RepeatedPosition {
position: *position,
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use polyc_crypto::approval::{ApprovalSigner, admin_model_change_payload};
use polyc_crypto::signing_role::{SigningKeyIdentity, TrustedKey};
use super::*;
fn record(position: u64, payload: Vec<u8>) -> (u64, Event) {
(position, Event::new(kinds::ADMIN_MODEL_CHANGE, payload))
}
fn change(signer: &ApprovalSigner, principal: &str, new_model: &str) -> Vec<u8> {
admin_model_change_payload(
principal,
"prov-a",
"model-a",
"prov-b",
new_model,
1_750_000_000_000,
signer,
)
.0
}
fn identity(signer: &ApprovalSigner) -> SigningKeyIdentity {
SigningKeyIdentity::for_public_key::<ApprovalRole>(signer.public_key_bytes()).unwrap()
}
fn rotated_trust(
current: &ApprovalSigner,
retired: &ApprovalSigner,
) -> RoleTrustSet<ApprovalRole> {
RoleTrustSet::checked(vec![
TrustedKey::current(identity(current)),
TrustedKey::retired(identity(retired)),
])
.unwrap()
}
#[test]
fn every_status_appears_in_all() {
for status in AdminSignatureStatus::ALL {
let spelled = match status {
AdminSignatureStatus::VerifiedCurrent => "verified_current",
AdminSignatureStatus::VerifiedRetired => "verified_retired",
AdminSignatureStatus::Unverified => "unverified",
AdminSignatureStatus::Malformed => "malformed",
};
assert_eq!(status.as_str(), spelled);
}
assert_eq!(AdminSignatureStatus::ALL.len(), 4);
}
#[test]
fn a_current_signature_verifies_as_current() {
let current = ApprovalSigner::from_seed(1);
let retired = ApprovalSigner::from_seed(2);
let trust = rotated_trust(¤t, &retired);
let events = vec![record(1, change(¤t, "admin:root", "model-b"))];
let facts = fold_administrator_audit(&events, &trust).unwrap();
assert_eq!(facts.model_changes.len(), 1);
assert_eq!(
facts.model_changes[0].signature_status,
AdminSignatureStatus::VerifiedCurrent
);
assert_eq!(facts.model_changes[0].principal, "admin:root");
assert_eq!(facts.model_changes[0].new_model, "model-b");
assert_eq!(facts.model_changes[0].position, 1);
}
#[test]
fn a_retired_signature_verifies_as_retired() {
let current = ApprovalSigner::from_seed(1);
let retired = ApprovalSigner::from_seed(2);
let trust = rotated_trust(¤t, &retired);
let events = vec![record(1, change(&retired, "admin:root", "model-b"))];
let facts = fold_administrator_audit(&events, &trust).unwrap();
assert_eq!(
facts.model_changes[0].signature_status,
AdminSignatureStatus::VerifiedRetired
);
}
#[test]
fn key_order_does_not_decide_the_status() {
let current = ApprovalSigner::from_seed(1);
let retired = ApprovalSigner::from_seed(2);
let reversed = RoleTrustSet::<ApprovalRole>::checked(vec![
TrustedKey::retired(identity(&retired)),
TrustedKey::current(identity(¤t)),
])
.unwrap();
let events = vec![
record(1, change(¤t, "admin:root", "model-b")),
record(2, change(&retired, "admin:root", "model-c")),
];
let facts = fold_administrator_audit(&events, &reversed).unwrap();
assert_eq!(
facts.model_changes[0].signature_status,
AdminSignatureStatus::VerifiedCurrent
);
assert_eq!(
facts.model_changes[1].signature_status,
AdminSignatureStatus::VerifiedRetired
);
}
#[test]
fn a_foreign_signature_reads_unverified_and_keeps_its_fields() {
let current = ApprovalSigner::from_seed(1);
let foreign = ApprovalSigner::from_seed(9);
let trust = RoleTrustSet::<ApprovalRole>::current(¤t);
let events = vec![record(1, change(&foreign, "attacker", "evil-model"))];
let facts = fold_administrator_audit(&events, &trust).unwrap();
assert_eq!(
facts.model_changes[0].signature_status,
AdminSignatureStatus::Unverified
);
assert_eq!(
facts.model_changes[0].principal, "attacker",
"the evidence of what was written is the point of keeping the row"
);
}
#[test]
fn a_tampered_record_reads_unverified_and_is_not_dropped() {
let current = ApprovalSigner::from_seed(1);
let trust = RoleTrustSet::<ApprovalRole>::current(¤t);
let genuine = change(¤t, "admin:root", "model-b");
let mut value: serde_json::Value = serde_json::from_slice(&genuine).unwrap();
value["new_model"] = serde_json::json!("evil-model");
let events = vec![record(1, value.to_string().into_bytes())];
let facts = fold_administrator_audit(&events, &trust).unwrap();
assert_eq!(facts.model_changes.len(), 1);
assert_eq!(
facts.model_changes[0].signature_status,
AdminSignatureStatus::Unverified
);
assert_eq!(facts.model_changes[0].new_model, "evil-model");
}
#[test]
fn a_malformed_record_is_a_row_with_empty_fields() {
let trust = RoleTrustSet::<ApprovalRole>::current(&ApprovalSigner::from_seed(1));
let events = vec![record(1, b"{\"not\":\"a record\"}".to_vec())];
let facts = fold_administrator_audit(&events, &trust).unwrap();
assert_eq!(facts.model_changes.len(), 1, "the record is not dropped");
let row = &facts.model_changes[0];
assert_eq!(row.signature_status, AdminSignatureStatus::Malformed);
assert_eq!(row.position, 1);
assert!(row.principal.is_empty());
assert!(row.previous_provider.is_empty());
assert!(row.previous_model.is_empty());
assert!(row.new_provider.is_empty());
assert!(row.new_model.is_empty());
assert_eq!(row.changed_at_ms, 0);
}
#[test]
fn an_undeclared_kind_refuses_the_generation() {
let signer = ApprovalSigner::from_seed(1);
let trust = RoleTrustSet::<ApprovalRole>::current(&signer);
let events = vec![
record(1, change(&signer, "admin:root", "model-b")),
(2, Event::new("turn_start", Vec::new())),
];
assert_eq!(
fold_administrator_audit(&events, &trust),
Err(AdministratorAuditError::UndeclaredKind { position: 2 })
);
}
#[test]
fn a_turn_tagged_kind_is_undeclared() {
let signer = ApprovalSigner::from_seed(1);
let trust = RoleTrustSet::<ApprovalRole>::current(&signer);
let tagged = format!("{}:{}", kinds::ADMIN_MODEL_CHANGE, uuid::Uuid::nil());
let events = vec![(1, Event::new(tagged, change(&signer, "a", "m")))];
assert_eq!(
fold_administrator_audit(&events, &trust),
Err(AdministratorAuditError::UndeclaredKind { position: 1 })
);
}
#[test]
fn a_repeated_position_is_refused() {
let signer = ApprovalSigner::from_seed(1);
let trust = RoleTrustSet::<ApprovalRole>::current(&signer);
let events = vec![
record(1, change(&signer, "admin:root", "model-b")),
record(1, change(&signer, "admin:root", "model-c")),
];
assert_eq!(
fold_administrator_audit(&events, &trust),
Err(AdministratorAuditError::RepeatedPosition { position: 1 })
);
}
#[test]
fn rows_follow_position_order() {
let signer = ApprovalSigner::from_seed(1);
let trust = RoleTrustSet::<ApprovalRole>::current(&signer);
let events = vec![
record(4, change(&signer, "admin:root", "model-b")),
record(9, change(&signer, "admin:root", "model-c")),
record(11, change(&signer, "admin:root", "model-d")),
];
let facts = fold_administrator_audit(&events, &trust).unwrap();
let positions: Vec<u64> = facts.model_changes.iter().map(|row| row.position).collect();
assert_eq!(positions, vec![4, 9, 11]);
}
#[test]
fn an_empty_prefix_folds_to_no_rows() {
let trust = RoleTrustSet::<ApprovalRole>::current(&ApprovalSigner::from_seed(1));
assert_eq!(
fold_administrator_audit(&[], &trust).unwrap(),
AdministratorAuditFacts::default()
);
}
}