use std::fmt;
use std::str::FromStr;
pub const ANN_PROOF: &str = "eu.pulseengine.source.proof";
pub const ANN_PROOF_SIGNER: &str = "eu.pulseengine.source.proof-signer";
pub const ANN_PROOF_ASSERTS: &str = "eu.pulseengine.source.proof-asserts";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum IngestProof {
CosignSums,
BuildProvenance,
Unverified,
}
impl IngestProof {
pub fn as_str(self) -> &'static str {
match self {
IngestProof::CosignSums => "cosign-sums",
IngestProof::BuildProvenance => "build-provenance",
IngestProof::Unverified => "unverified",
}
}
pub fn is_verified(self) -> bool {
!matches!(self, IngestProof::Unverified)
}
pub fn label(proof: Option<IngestProof>) -> &'static str {
match proof {
Some(p) => p.as_str(),
None => "unrecorded",
}
}
}
impl fmt::Display for IngestProof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error(
"unknown ingestion proof '{0}': this varve does not know what that mechanism asserts \
(expected one of cosign-sums, build-provenance, unverified). The payload's bytes still \
verify against the signed digest — only the claim about how they were vouched for is \
unreadable here. A newer varve may know it."
)]
pub struct UnknownProof(pub String);
impl FromStr for IngestProof {
type Err = UnknownProof;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"cosign-sums" => Ok(IngestProof::CosignSums),
"build-provenance" => Ok(IngestProof::BuildProvenance),
"unverified" => Ok(IngestProof::Unverified),
other => Err(UnknownProof(other.to_string())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL: &[IngestProof] = &[
IngestProof::CosignSums,
IngestProof::BuildProvenance,
IngestProof::Unverified,
];
#[test]
fn the_wire_spelling_round_trips_and_is_what_the_spec_and_the_annotation_share() {
for p in ALL {
assert_eq!(p.as_str().parse::<IngestProof>().unwrap(), *p);
let via_serde: IngestProof =
serde_json::from_str(&format!("\"{}\"", p.as_str())).unwrap();
assert_eq!(via_serde, *p);
}
assert_eq!(IngestProof::CosignSums.as_str(), "cosign-sums");
assert_eq!(IngestProof::BuildProvenance.as_str(), "build-provenance");
assert_eq!(IngestProof::Unverified.as_str(), "unverified");
}
#[test]
fn absent_is_unrecorded_and_only_unverified_is_unverified() {
assert_eq!(IngestProof::label(None), "unrecorded");
for p in ALL {
assert_eq!(IngestProof::label(Some(*p)), p.as_str());
assert_ne!(
IngestProof::label(Some(*p)),
"unrecorded",
"a declared mechanism must never read as the absent case"
);
}
assert!(IngestProof::CosignSums.is_verified());
assert!(IngestProof::BuildProvenance.is_verified());
assert!(
!IngestProof::Unverified.is_verified(),
"the whole point of the state is that nothing vouched for it"
);
}
#[test]
fn an_unknown_mechanism_is_reported_verbatim() {
let err = "notary-v2".parse::<IngestProof>().unwrap_err();
assert_eq!(err, UnknownProof("notary-v2".into()));
assert!(err.to_string().contains("notary-v2"));
for bad in ["cosign", "CosignSums", "provenance", ""] {
assert!(bad.parse::<IngestProof>().is_err(), "{bad} must be refused");
}
}
}