chio-workflow-preflight 0.1.2

Read-only workflow preflight verifier for Chio
Documentation
use serde::{Deserialize, Serialize};

pub const WORKFLOW_PREFLIGHT_PLAN_SCHEMA: &str = "chio.workflow.preflight-plan.v1";
pub const WORKFLOW_PREFLIGHT_REPORT_SCHEMA: &str = "chio.workflow.preflight-report.v1";

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum WorkflowPreflightError {
    #[error("unsupported workflow preflight schema for {field}: {schema}")]
    UnsupportedSchema { field: &'static str, schema: String },
    #[error("invalid workflow preflight plan: {0}")]
    InvalidPlan(String),
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPreflightPlan {
    pub schema: String,
    pub id: String,
    pub issued_at: String,
    pub parent_task: WorkflowPreflightParentTask,
    pub child_tasks: Vec<WorkflowPreflightChildTask>,
    pub route_plans: Vec<WorkflowRoutePlanPreflight>,
    pub approvals: Vec<WorkflowApprovalPreflight>,
    pub registry_support: WorkflowRegistrySupport,
    pub budget_pool: WorkflowBudgetPool,
    pub revocation: WorkflowRevocationPreflight,
    pub planning_artifacts: Vec<WorkflowPlanningArtifact>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPreflightParentTask {
    pub task_id: String,
    pub scope: WorkflowPreflightScope,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPreflightChildTask {
    pub task_id: String,
    pub parent_task_id: String,
    pub requested_scope: WorkflowPreflightScope,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPreflightScope {
    pub actions: Vec<String>,
    pub resources: Vec<String>,
    pub route_refs: Vec<String>,
    pub approval_refs: Vec<String>,
    pub required_schemas: Vec<String>,
    pub budget_minor: u64,
    pub currency: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowRoutePlanPreflight {
    pub route_ref: String,
    pub supported: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowApprovalPreflight {
    pub approval_ref: String,
    pub status: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowRegistrySupport {
    pub supported_schemas: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowBudgetPool {
    pub currency: String,
    pub total_minor: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowRevocationPreflight {
    pub epoch_id: String,
    pub root_sha256: String,
    pub status: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPlanningArtifact {
    pub artifact_ref: String,
    pub artifact_class: String,
    pub satisfies_claims: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkflowPreflightVerdict {
    Accepted,
    Rejected,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPreflightReport {
    pub schema: String,
    pub id: String,
    pub issued_at: String,
    pub plan_id: String,
    pub verdict: WorkflowPreflightVerdict,
    pub evidence_class: String,
    pub verified_claims: Vec<String>,
    pub rejected_checks: Vec<WorkflowPreflightCheck>,
    pub live_authority_claims: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkflowPreflightCheck {
    pub code: String,
    pub message: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub task_id: Option<String>,
}

impl WorkflowPreflightCheck {
    pub(super) fn new(
        code: &'static str,
        message: impl Into<String>,
        task_id: Option<String>,
    ) -> Self {
        Self {
            code: code.to_string(),
            message: message.into(),
            task_id,
        }
    }
}