use crate::meta::component::ComponentSpec;
use sha2::Digest;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MetaVerdict {
Verified {
sampled_boundaries: Vec<String>,
},
Counterexample {
id: String,
},
PreconditionFailure {
message: String,
},
}
impl MetaVerdict {
#[inline]
pub fn counterexample(id: impl Into<String>) -> Self {
let id = id.into();
debug_assert!(!id.is_empty(), "Fix: Counterexample id must be non-empty");
Self::Counterexample { id }
}
#[inline]
pub fn verified(boundaries: Vec<String>) -> Self {
debug_assert!(
!boundaries.iter().any(|b| b.is_empty()),
"Fix: every boundary id in a Verified manifest must be non-empty"
);
Self::Verified {
sampled_boundaries: boundaries,
}
}
#[must_use]
#[inline]
pub fn is_valid_counterexample_id(id: &str) -> bool {
!id.is_empty()
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum MetaOracleKind {
Contract,
ReferenceComponent,
SpecTable,
Property,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum AttestationDecision {
Accept,
Reject,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct HumanReviewAttestation {
pub finding_id: String,
pub finding_sha256: String,
pub reviewer_git_identity: String,
pub timestamp: String,
pub decision: AttestationDecision,
}
#[derive(Debug, thiserror::Error)]
pub enum AttestationError {
#[error("missing human review attestation. Fix: add conform/attestations/{finding_id}.toml with finding_sha256, reviewer_git_identity, timestamp, and decision.")]
Missing {
finding_id: String,
},
#[error("failed to read human review attestation: {0}. Fix: restore a readable TOML attestation file.")]
Read(#[from] std::io::Error),
#[error(
"invalid human review attestation TOML: {0}. Fix: write a valid attestation document."
)]
Toml(#[from] toml::de::Error),
#[error("attestation missing `{field}`. Fix: add the required field.")]
MissingField {
field: &'static str,
},
#[error("attestation field `{field}` is invalid. Fix: use the documented attestation format.")]
InvalidField {
field: &'static str,
},
#[error(
"attestation SHA256 mismatch. Fix: recompute finding_sha256 over the exact finding text."
)]
HashMismatch,
#[error("human review rejected this Property-grade finding. Fix: keep the finding open or replace the Property oracle with a stronger oracle.")]
Rejected,
}
pub trait MetaOracle: Sync + Send {
fn kind(&self) -> MetaOracleKind;
fn applicable_to(&self, component: &ComponentSpec, input_id: &str) -> bool;
}
#[must_use]
#[inline]
pub fn property_finding_id(component_name: &str, input_id: &str) -> String {
sanitize_attestation_id(&format!("{component_name}.{input_id}"))
}
#[must_use]
#[inline]
pub fn property_finding_text(component_name: &str, input_id: &str) -> String {
format!(
"Property-grade meta finding: component={component_name}; input_id={input_id}; oracle=Property"
)
}
#[inline]
pub fn validate_property_attestation(
component_name: &str,
input_id: &str,
) -> Result<HumanReviewAttestation, AttestationError> {
let finding_id = property_finding_id(component_name, input_id);
validate_property_attestation_at(
default_attestation_path(&finding_id),
&finding_id,
&property_finding_text(component_name, input_id),
)
}
#[inline]
pub fn validate_property_attestation_at(
path: PathBuf,
finding_id: &str,
finding_text: &str,
) -> Result<HumanReviewAttestation, AttestationError> {
if !path.exists() {
return Err(AttestationError::Missing {
finding_id: finding_id.to_string(),
});
}
let content = std::fs::read_to_string(path)?;
let value: toml::Value = content.parse()?;
let attestation = parse_attestation(&value)?;
if attestation.finding_id != finding_id {
return Err(AttestationError::InvalidField {
field: "finding_id",
});
}
if attestation.finding_sha256 != hex::encode(sha2::Sha256::digest(finding_text.as_bytes())) {
return Err(AttestationError::HashMismatch);
}
if matches!(attestation.decision, AttestationDecision::Reject) {
return Err(AttestationError::Rejected);
}
Ok(attestation)
}
fn default_attestation_path(finding_id: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("attestations")
.join(format!("{finding_id}.toml"))
}
fn parse_attestation(value: &toml::Value) -> Result<HumanReviewAttestation, AttestationError> {
let finding_id = required_string(value, "finding_id")?;
let finding_sha256 = required_string(value, "finding_sha256")?;
if finding_sha256.len() != 64 || !finding_sha256.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(AttestationError::InvalidField {
field: "finding_sha256",
});
}
let reviewer_git_identity = required_string(value, "reviewer_git_identity")?;
if reviewer_git_identity.trim().is_empty() {
return Err(AttestationError::InvalidField {
field: "reviewer_git_identity",
});
}
let timestamp = required_string(value, "timestamp")?;
if timestamp.trim().is_empty() {
return Err(AttestationError::InvalidField { field: "timestamp" });
}
let decision = match required_string(value, "decision")?.as_str() {
"accept" => AttestationDecision::Accept,
"reject" => AttestationDecision::Reject,
_ => return Err(AttestationError::InvalidField { field: "decision" }),
};
Ok(HumanReviewAttestation {
finding_id,
finding_sha256,
reviewer_git_identity,
timestamp,
decision,
})
}
fn required_string(value: &toml::Value, field: &'static str) -> Result<String, AttestationError> {
value
.get(field)
.and_then(toml::Value::as_str)
.map(str::to_string)
.ok_or(AttestationError::MissingField { field })
}
fn sanitize_attestation_id(raw: &str) -> String {
raw.bytes()
.map(|byte| match byte {
b'a'..=b'z' | b'0'..=b'9' | b'_' | b'.' => byte as char,
b'A'..=b'Z' => byte.to_ascii_lowercase() as char,
_ => '_',
})
.collect()
}
#[must_use]
#[inline]
pub fn resolve(component: &ComponentSpec, input_id: &str) -> MetaOracleKind {
let row_has_post = component.spec_table.iter().any(|row| {
row.input_id == input_id
&& row.contract_ids.iter().any(|cid| {
component.contracts.iter().any(|c| {
c.id == *cid && matches!(c.kind, crate::meta::component::ContractKind::Post)
})
})
});
if row_has_post {
return MetaOracleKind::Contract;
}
if component
.spec_table
.iter()
.any(|row| row.input_id == input_id)
{
return MetaOracleKind::SpecTable;
}
if component.preferred_oracle == MetaOracleKind::ReferenceComponent {
return MetaOracleKind::ReferenceComponent;
}
MetaOracleKind::Property
}