use std::path::Path;
use auths_id::keri::types::Prefix;
use auths_transparency::{WitnessPolicy, WitnessPolicyError, ceiling_for_policy_load};
use auths_verifier::IdentityDID;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub use auths_verifier::evidence_pack::{
ComplianceFramework, EVIDENCE_PACK_SCHEMA_VERSION, EvidencePack, EvidencePackError,
EvidenceRow, RowVerdict, TransparencyInclusion, verify_evidence_pack_offline,
verify_transparency_inclusion,
};
use crate::context::AuthsContext;
use crate::domains::org::audit::classify_authority_at_signing;
use crate::domains::org::bundle::build_org_bundle;
use crate::domains::org::error::OrgError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ComplianceQueryError {
#[error("authority classification failed: {0}")]
Authority(#[from] OrgError),
#[error("canonicalization failed: {0}")]
Canonicalize(String),
#[error("org signing failed: {0}")]
Signing(String),
#[error("decode failed: {0}")]
Decode(String),
#[error("verification failed: {0}")]
Verification(String),
#[error("offline verification failed: {0}")]
OfflineVerification(String),
#[error("release anchoring failed: {0}")]
Anchor(#[from] auths_id::keri::AnchorError),
#[error("registry access failed: {0}")]
Registry(String),
#[error("invalid release attestation: {0}")]
InvalidRelease(String),
#[error("anchored release attestation {0} does not match its KEL seal digest")]
TamperedRelease(String),
}
impl From<EvidencePackError> for ComplianceQueryError {
fn from(e: EvidencePackError) -> Self {
match e {
EvidencePackError::Canonicalize(m) => Self::Canonicalize(m),
EvidencePackError::Decode(m) => Self::Decode(m),
EvidencePackError::OfflineVerification(m) => Self::OfflineVerification(m),
other => Self::OfflineVerification(other.to_string()),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReleaseRecord {
pub artifact_digest: String,
pub signer_prefix: Prefix,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signed_at: Option<u128>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transparency: Option<TransparencyInclusion>,
}
pub fn load_witness_policy(path: Option<&Path>) -> Result<WitnessPolicy, WitnessPolicyError> {
match path {
Some(p) => WitnessPolicy::load(p),
None => Err(WitnessPolicyError::NotFound {
path: "<AUTHS_WITNESS_POLICY_PATH unset>".into(),
}),
}
}
fn classify_rows(
ctx: &AuthsContext,
org_prefix: &Prefix,
releases: &[ReleaseRecord],
) -> Result<Vec<EvidenceRow>, ComplianceQueryError> {
let mut rows = Vec::with_capacity(releases.len());
for r in releases {
let authority_at_release =
classify_authority_at_signing(ctx, org_prefix, &r.signer_prefix, r.signed_at)?;
let signer = IdentityDID::try_from(&r.signer_prefix)
.map_err(|e| ComplianceQueryError::InvalidRelease(e.to_string()))?;
rows.push(EvidenceRow {
artifact_digest: r.artifact_digest.clone(),
signer,
authority_at_release,
signed_at: r.signed_at,
transparency: r.transparency.clone(),
});
}
Ok(rows)
}
#[allow(clippy::too_many_arguments)]
pub fn build_evidence_pack(
ctx: &AuthsContext,
org: IdentityDID,
org_prefix: &Prefix,
period: impl Into<String>,
framework: ComplianceFramework,
releases: &[ReleaseRecord],
witness_policy: &Result<WitnessPolicy, WitnessPolicyError>,
generated_at: DateTime<Utc>,
) -> Result<EvidencePack, ComplianceQueryError> {
let rows = classify_rows(ctx, org_prefix, releases)?;
Ok(EvidencePack {
schema_version: EVIDENCE_PACK_SCHEMA_VERSION,
org,
period: period.into(),
framework,
equivocation_visibility: ceiling_for_policy_load(witness_policy),
generated_at,
rows,
org_bundle: None,
})
}
#[allow(clippy::too_many_arguments)]
pub fn build_offline_evidence_pack(
ctx: &AuthsContext,
org: IdentityDID,
org_prefix: &Prefix,
period: impl Into<String>,
framework: ComplianceFramework,
releases: &[ReleaseRecord],
witness_policy: &Result<WitnessPolicy, WitnessPolicyError>,
generated_at: DateTime<Utc>,
) -> Result<EvidencePack, ComplianceQueryError> {
let mut pack = build_evidence_pack(
ctx,
org,
org_prefix,
period,
framework,
releases,
witness_policy,
generated_at,
)?;
pack.org_bundle = Some(build_org_bundle(ctx, org_prefix)?);
Ok(pack)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use auths_transparency::HonestyCeiling;
use auths_verifier::org_bundle::AuthorityAtSigning;
fn fixed_now() -> DateTime<Utc> {
DateTime::parse_from_rfc3339("2026-06-08T00:00:00Z")
.unwrap()
.with_timezone(&Utc)
}
fn single_operator_ceiling() -> HonestyCeiling {
ceiling_for_policy_load(&Err(WitnessPolicyError::NotFound {
path: "<unset>".into(),
}))
}
fn sample_pack() -> EvidencePack {
EvidencePack {
schema_version: EVIDENCE_PACK_SCHEMA_VERSION,
org: IdentityDID::parse("did:keri:EOrg").unwrap(),
period: "2026-Q3".into(),
framework: ComplianceFramework::Slsa,
equivocation_visibility: single_operator_ceiling(),
generated_at: fixed_now(),
rows: vec![EvidenceRow {
artifact_digest: "sha256:aa".into(),
signer: IdentityDID::parse("did:keri:EAlice").unwrap(),
authority_at_release: AuthorityAtSigning::AuthorizedBeforeRevocation,
signed_at: Some(7),
transparency: None,
}],
org_bundle: None,
}
}
#[test]
fn pack_embeds_single_operator_ceiling_not_a_bare_flag() {
let pack = sample_pack();
assert!(!pack.equivocation_visibility.policy_met);
let json = pack.canonicalize().unwrap();
assert!(!json.contains("non_equivocation"));
assert!(json.contains("not yet independent"));
}
}