#[cfg(feature = "ai-transparency")]
mod image_provenance;
pub mod module;
pub use module::AiTransparencyModule;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AiProvenanceKind {
TrainedAlgorithmicMedia,
CompositeWithTrainedAlgorithmicMedia,
CompositeSynthetic,
VirtualRecording,
TrainedAlgorithmicData,
}
impl AiProvenanceKind {
pub fn as_str(self) -> &'static str {
match self {
Self::TrainedAlgorithmicMedia => "trained_algorithmic_media",
Self::CompositeWithTrainedAlgorithmicMedia => {
"composite_with_trained_algorithmic_media"
}
Self::CompositeSynthetic => "composite_synthetic",
Self::VirtualRecording => "virtual_recording",
Self::TrainedAlgorithmicData => "trained_algorithmic_data",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManifestValidation {
Invalid,
Valid,
Trusted,
}
impl ManifestValidation {
pub fn as_str(self) -> &'static str {
match self {
Self::Invalid => "invalid",
Self::Valid => "valid",
Self::Trusted => "trusted",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageProvenanceFinding {
pub rule_id: String,
pub image_url: String,
pub provenance: AiProvenanceKind,
pub validation: ManifestValidation,
pub generator: Option<String>,
pub measurement_type: String,
pub evidence: String,
pub message: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AiTransparencyAnalysis {
pub findings: Vec<ImageProvenanceFinding>,
pub images_checked: usize,
}
pub fn finding_message_text(rule_id: &str, en: bool) -> String {
match rule_id {
"ai_transparency.c2pa_provenance" => if en {
"This image carries a C2PA Content Credentials manifest asserting AI/algorithmic generation. Verify a human-visible disclosure exists for this image (EU AI Act Art. 50 transparency duties)."
} else {
"Dieses Bild trägt ein C2PA-\"Content Credentials\"-Manifest, das eine KI-/algorithmische Erzeugung ausweist. Prüfen Sie, ob für dieses Bild eine für Menschen wahrnehmbare Kennzeichnung existiert (EU-AI-Act-Transparenzpflichten, Art. 50)."
}
.to_string(),
_ => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn english_message_text_has_no_known_german_leaks() {
let has_umlaut = |s: &str| s.chars().any(|c| "äöüÄÖÜß".contains(c));
let msg = finding_message_text("ai_transparency.c2pa_provenance", true);
assert!(!msg.is_empty());
assert!(!has_umlaut(&msg), "EN message leaks German: {msg}");
}
#[test]
fn german_message_text_differs_from_english() {
let en = finding_message_text("ai_transparency.c2pa_provenance", true);
let de = finding_message_text("ai_transparency.c2pa_provenance", false);
assert_ne!(en, de);
}
#[test]
fn unknown_rule_id_yields_empty_message() {
assert_eq!(finding_message_text("ai_transparency.unknown", true), "");
}
}