use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{ClaimCeiling, PolicyContribution, PolicyError, PolicyOutcome};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum ProvenanceClass {
UnknownProvenance,
SimulatedOrHypothetical,
ExternalClaimed,
SummaryDerived,
RuntimeDerived,
ToolObserved,
OperatorAttested,
}
impl ProvenanceClass {
#[must_use]
pub const fn is_derived(self) -> bool {
matches!(
self,
Self::RuntimeDerived | Self::SummaryDerived | Self::SimulatedOrHypothetical
)
}
#[must_use]
pub const fn can_corroborate(self) -> bool {
matches!(self, Self::ToolObserved | Self::OperatorAttested)
}
#[must_use]
pub const fn claim_ceiling(self) -> ClaimCeiling {
match self {
Self::UnknownProvenance | Self::SimulatedOrHypothetical => ClaimCeiling::DevOnly,
Self::ExternalClaimed | Self::SummaryDerived | Self::RuntimeDerived => {
ClaimCeiling::LocalUnsigned
}
Self::ToolObserved | Self::OperatorAttested => ClaimCeiling::AuthorityGrade,
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum SemanticTrustClass {
Unknown,
CandidateOnly,
SingleFamily,
Corroborated,
FalsificationTested,
}
impl SemanticTrustClass {
#[must_use]
pub const fn claim_ceiling(self) -> ClaimCeiling {
match self {
Self::Unknown => ClaimCeiling::DevOnly,
Self::CandidateOnly | Self::SingleFamily => ClaimCeiling::LocalUnsigned,
Self::Corroborated => ClaimCeiling::SignedLocalLedger,
Self::FalsificationTested => ClaimCeiling::AuthorityGrade,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SemanticUse {
CandidateMemory,
DefaultContext,
AdvisoryDoctrine,
HighForceDoctrine,
TrustedExternalUse,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SemanticTrustInput {
pub intended_use: SemanticUse,
pub provenance_classes: Vec<ProvenanceClass>,
pub independent_source_families: u16,
pub falsification_evidence: bool,
pub unresolved_unknowns: bool,
}
impl SemanticTrustInput {
#[must_use]
pub fn new(intended_use: SemanticUse) -> Self {
Self {
intended_use,
provenance_classes: Vec::new(),
independent_source_families: 0,
falsification_evidence: false,
unresolved_unknowns: true,
}
}
#[must_use]
pub fn with_provenance<I>(mut self, provenance_classes: I) -> Self
where
I: IntoIterator<Item = ProvenanceClass>,
{
self.provenance_classes = provenance_classes.into_iter().collect();
self
}
#[must_use]
pub const fn with_independent_source_families(mut self, count: u16) -> Self {
self.independent_source_families = count;
self
}
#[must_use]
pub const fn with_falsification_evidence(mut self, present: bool) -> Self {
self.falsification_evidence = present;
self
}
#[must_use]
pub const fn with_unresolved_unknowns(mut self, present: bool) -> Self {
self.unresolved_unknowns = present;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SemanticTrustReport {
pub semantic_trust: SemanticTrustClass,
pub weakest_provenance: ProvenanceClass,
pub policy_outcome: PolicyOutcome,
pub claim_ceiling: ClaimCeiling,
pub reasons: Vec<String>,
}
impl SemanticTrustReport {
#[must_use]
pub const fn is_allow(&self) -> bool {
matches!(self.policy_outcome, PolicyOutcome::Allow)
}
pub fn policy_contribution(&self) -> Result<PolicyContribution, PolicyError> {
PolicyContribution::new(
"semantic_trust.provenance",
self.policy_outcome,
self.reasons
.first()
.cloned()
.unwrap_or_else(|| "semantic trust evaluated".to_string()),
)
}
}
#[must_use]
pub fn evaluate_semantic_trust(input: &SemanticTrustInput) -> SemanticTrustReport {
let weakest_provenance = input
.provenance_classes
.iter()
.copied()
.min()
.unwrap_or(ProvenanceClass::UnknownProvenance);
let has_unknown = input.provenance_classes.is_empty()
|| input.unresolved_unknowns
|| input
.provenance_classes
.contains(&ProvenanceClass::UnknownProvenance);
let corroborating_families = input
.provenance_classes
.iter()
.filter(|class| class.can_corroborate())
.count() as u16;
let runtime_or_weaker_only = !input.provenance_classes.is_empty()
&& input
.provenance_classes
.iter()
.all(|class| !class.can_corroborate());
let semantic_trust = if has_unknown {
SemanticTrustClass::Unknown
} else if input.falsification_evidence
&& input.independent_source_families >= 2
&& corroborating_families >= 1
{
SemanticTrustClass::FalsificationTested
} else if input.independent_source_families >= 2 && corroborating_families >= 1 {
SemanticTrustClass::Corroborated
} else if !input.provenance_classes.is_empty() {
SemanticTrustClass::SingleFamily
} else {
SemanticTrustClass::CandidateOnly
};
let mut reasons = Vec::new();
let policy_outcome = match input.intended_use {
SemanticUse::CandidateMemory => {
if has_unknown {
reasons.push("unknown provenance retained as candidate-only".to_string());
PolicyOutcome::Warn
} else {
reasons
.push("candidate memory may retain classified semantic evidence".to_string());
PolicyOutcome::Allow
}
}
SemanticUse::DefaultContext | SemanticUse::AdvisoryDoctrine => {
if has_unknown {
reasons.push("unknown provenance cannot enter default authority use".to_string());
PolicyOutcome::Quarantine
} else if runtime_or_weaker_only {
reasons.push(
"runtime-derived or weak-only provenance is advisory/candidate only"
.to_string(),
);
PolicyOutcome::Warn
} else {
reasons.push("semantic provenance permits bounded advisory use".to_string());
PolicyOutcome::Allow
}
}
SemanticUse::HighForceDoctrine | SemanticUse::TrustedExternalUse => {
if has_unknown {
reasons.push("unknown provenance cannot satisfy high-authority use".to_string());
PolicyOutcome::Reject
} else if runtime_or_weaker_only {
reasons.push(
"runtime-only or weak-only support cannot satisfy high-authority use"
.to_string(),
);
PolicyOutcome::Reject
} else if semantic_trust < SemanticTrustClass::FalsificationTested {
reasons.push(
"high-authority use requires corroboration plus falsification evidence"
.to_string(),
);
PolicyOutcome::Reject
} else {
reasons.push(
"semantic trust permits high-authority use subject to other gates".to_string(),
);
PolicyOutcome::Allow
}
}
};
let claim_ceiling = weakest_provenance
.claim_ceiling()
.min(semantic_trust.claim_ceiling())
.min(policy_outcome.claim_ceiling());
SemanticTrustReport {
semantic_trust,
weakest_provenance,
policy_outcome,
claim_ceiling,
reasons,
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn provenance_wire_strings_are_stable() {
assert_eq!(
serde_json::to_value(ProvenanceClass::OperatorAttested).unwrap(),
json!("operator_attested")
);
assert_eq!(
serde_json::to_value(ProvenanceClass::ToolObserved).unwrap(),
json!("tool_observed")
);
assert_eq!(
serde_json::to_value(ProvenanceClass::RuntimeDerived).unwrap(),
json!("runtime_derived")
);
assert_eq!(
serde_json::to_value(ProvenanceClass::UnknownProvenance).unwrap(),
json!("unknown_provenance")
);
}
#[test]
fn unknown_provenance_fails_closed_for_high_force_doctrine() {
let report = evaluate_semantic_trust(
&SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
.with_provenance([ProvenanceClass::UnknownProvenance])
.with_independent_source_families(1)
.with_unresolved_unknowns(true),
);
assert_eq!(report.semantic_trust, SemanticTrustClass::Unknown);
assert_eq!(report.policy_outcome, PolicyOutcome::Reject);
assert_eq!(report.claim_ceiling, ClaimCeiling::DevOnly);
}
#[test]
fn runtime_only_support_cannot_promote_high_force_doctrine() {
let report = evaluate_semantic_trust(
&SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
.with_provenance([
ProvenanceClass::RuntimeDerived,
ProvenanceClass::SummaryDerived,
])
.with_independent_source_families(2)
.with_falsification_evidence(true)
.with_unresolved_unknowns(false),
);
assert_eq!(report.policy_outcome, PolicyOutcome::Reject);
assert_eq!(report.semantic_trust, SemanticTrustClass::SingleFamily);
assert!(report
.reasons
.iter()
.any(|reason| reason.contains("runtime-only")));
}
#[test]
fn corroborated_and_falsified_support_can_pass_semantic_gate() {
let report = evaluate_semantic_trust(
&SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
.with_provenance([
ProvenanceClass::ToolObserved,
ProvenanceClass::OperatorAttested,
])
.with_independent_source_families(2)
.with_falsification_evidence(true)
.with_unresolved_unknowns(false),
);
assert_eq!(
report.semantic_trust,
SemanticTrustClass::FalsificationTested
);
assert_eq!(report.policy_outcome, PolicyOutcome::Allow);
assert_eq!(report.claim_ceiling, ClaimCeiling::AuthorityGrade);
}
#[test]
fn policy_contribution_is_machine_readable() {
let report = evaluate_semantic_trust(
&SemanticTrustInput::new(SemanticUse::DefaultContext)
.with_provenance([ProvenanceClass::RuntimeDerived])
.with_independent_source_families(1)
.with_unresolved_unknowns(false),
);
let contribution = report.policy_contribution().unwrap();
assert_eq!(contribution.rule_id.as_str(), "semantic_trust.provenance");
assert_eq!(contribution.outcome, PolicyOutcome::Warn);
}
}