chio-governance 0.1.2

Chio generic governance charters and case evaluation
Documentation
use serde::{Deserialize, Serialize};

use crate::error::GovernanceAuthorizationError;
use crate::receipt::lineage::SignedExportEnvelope;
use crate::validation::{validate_non_empty, validate_sha256_hex};

pub const GOVERNANCE_RECEIPT_SCHEMA_V1: &str = "chio.governance-receipt.v1";

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GovernanceReceiptCaseKind {
    DestructiveAuthorization,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GovernanceReceiptArtifact {
    pub schema: String,
    pub receipt_id: String,
    pub authorizing_kernel: String,
    pub case_kind: GovernanceReceiptCaseKind,
    pub authorized_lease_id: String,
    pub workflow_id: String,
    pub step_sha256: String,
    pub issued_at_unix_ms: u64,
    pub expires_at_unix_ms: u64,
}

impl GovernanceReceiptArtifact {
    pub fn validate(&self) -> Result<(), GovernanceAuthorizationError> {
        if self.schema != GOVERNANCE_RECEIPT_SCHEMA_V1 {
            return Err(GovernanceAuthorizationError::UnsupportedSchema(
                self.schema.clone(),
            ));
        }
        validate_non_empty(&self.receipt_id, "receipt_id")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_non_empty(&self.authorizing_kernel, "authorizing_kernel")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_non_empty(&self.authorized_lease_id, "authorized_lease_id")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_non_empty(&self.workflow_id, "workflow_id")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_sha256_hex(&self.step_sha256, "step_sha256")?;
        if self.expires_at_unix_ms <= self.issued_at_unix_ms {
            return Err(GovernanceAuthorizationError::InvalidArtifact(
                "expires_at_unix_ms must be greater than issued_at_unix_ms".to_string(),
            ));
        }
        Ok(())
    }
}

pub type SignedGovernanceReceipt = SignedExportEnvelope<GovernanceReceiptArtifact>;

pub fn verify_destructive_authorization(
    receipt: &SignedGovernanceReceipt,
    expected_lease_id: &str,
    expected_workflow_id: &str,
    expected_step_sha256: &str,
    now_unix_ms: u64,
) -> Result<(), GovernanceAuthorizationError> {
    receipt.body.validate()?;
    validate_sha256_hex(expected_step_sha256, "expected_step_sha256")?;
    if !receipt
        .verify_signature()
        .map_err(|e| GovernanceAuthorizationError::Crypto(e.to_string()))?
    {
        return Err(GovernanceAuthorizationError::InvalidSignature);
    }
    if receipt.body.issued_at_unix_ms > now_unix_ms {
        return Err(GovernanceAuthorizationError::GovernanceReceiptNotYetValid(
            receipt.body.receipt_id.clone(),
        ));
    }
    if receipt.body.expires_at_unix_ms <= now_unix_ms {
        return Err(GovernanceAuthorizationError::LeaseExpiredOrUnknown(
            receipt.body.receipt_id.clone(),
        ));
    }
    if receipt.body.authorized_lease_id != expected_lease_id {
        return Err(GovernanceAuthorizationError::LeaseMismatch);
    }
    if receipt.body.workflow_id != expected_workflow_id {
        return Err(GovernanceAuthorizationError::WorkflowMismatch);
    }
    if receipt.body.step_sha256 != expected_step_sha256 {
        return Err(GovernanceAuthorizationError::StepHashMismatch);
    }
    Ok(())
}

pub fn verify_step_governance_boundary(
    destructive: bool,
    receipt: Option<&SignedGovernanceReceipt>,
    now_unix_ms: u64,
) -> Result<(), GovernanceAuthorizationError> {
    if !destructive {
        return Ok(());
    }
    let Some(receipt) = receipt else {
        return Err(GovernanceAuthorizationError::GovernanceReceiptRequired);
    };
    receipt.body.validate()?;
    if receipt.body.expires_at_unix_ms <= now_unix_ms {
        return Err(GovernanceAuthorizationError::LeaseExpiredOrUnknown(
            receipt.body.receipt_id.clone(),
        ));
    }
    if !receipt
        .verify_signature()
        .map_err(|e| GovernanceAuthorizationError::Crypto(e.to_string()))?
    {
        return Err(GovernanceAuthorizationError::InvalidSignature);
    }
    if receipt.body.issued_at_unix_ms > now_unix_ms {
        return Err(GovernanceAuthorizationError::GovernanceReceiptNotYetValid(
            receipt.body.receipt_id.clone(),
        ));
    }
    Ok(())
}