chio-core 0.1.2

Core types for the Chio protocol
Documentation
use std::collections::{HashMap, HashSet};

use super::*;

pub fn negotiate_extension(
    inventory: &ChioExtensionInventory,
    package: &OfficialStackPackage,
    manifest: &ChioExtensionManifest,
) -> ExtensionNegotiationReport {
    let mut reasons = Vec::new();

    if let Err(error) = validate_extension_inventory(inventory) {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::MalformedInventory,
            error.to_string(),
        ));
    }
    if let Err(error) = validate_official_stack_package(inventory, package) {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::MalformedOfficialStack,
            error.to_string(),
        ));
    }
    if let Err(error) = validate_extension_manifest(manifest) {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::MalformedManifest,
            error.to_string(),
        ));
    }
    if !reasons.is_empty() {
        return ExtensionNegotiationReport {
            schema: CHIO_EXTENSION_NEGOTIATION_SCHEMA.to_string(),
            official_stack_package_id: package.package_id.clone(),
            extension_id: manifest.extension_id.clone(),
            extension_point_id: manifest.extension_point_id.clone(),
            outcome: ExtensionNegotiationOutcome::Rejected,
            reasons,
        };
    }

    let points_by_id: HashMap<_, _> = inventory
        .extension_points
        .iter()
        .map(|point| (point.id.as_str(), point))
        .collect();
    let profiles: HashSet<_> = package
        .profiles
        .iter()
        .map(|profile| profile.id.as_str())
        .collect();
    let components: HashSet<_> = package
        .components
        .iter()
        .map(|component| component.id.as_str())
        .collect();

    if package.package_id != manifest.compatibility.official_stack_package_id {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::UnsupportedOfficialStack,
            format!(
                "manifest targets {}, expected {}",
                manifest.compatibility.official_stack_package_id, package.package_id
            ),
        ));
    }
    if package.chio_contract_version != manifest.compatibility.chio_contract_version {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::UnsupportedChioContract,
            format!(
                "manifest targets Chio {}, expected {}",
                manifest.compatibility.chio_contract_version, package.chio_contract_version
            ),
        ));
    }

    let Some(point) = points_by_id.get(manifest.extension_point_id.as_str()) else {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::UnknownExtensionPoint,
            format!(
                "extension point {} is not registered",
                manifest.extension_point_id
            ),
        ));
        return ExtensionNegotiationReport {
            schema: CHIO_EXTENSION_NEGOTIATION_SCHEMA.to_string(),
            official_stack_package_id: package.package_id.clone(),
            extension_id: manifest.extension_id.clone(),
            extension_point_id: manifest.extension_point_id.clone(),
            outcome: ExtensionNegotiationOutcome::Rejected,
            reasons,
        };
    };

    if manifest.distribution != ExtensionDistribution::OfficialFirstParty
        && !point.custom_implementations_allowed
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::OfficialOnlyPoint,
            format!(
                "extension point {} is reserved for official components",
                point.id
            ),
        ));
    }
    if manifest.distribution != ExtensionDistribution::OfficialFirstParty
        && point.stability == ExtensionStability::Internal
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::InternalOnlyPoint,
            format!("extension point {} is internal-only", point.id),
        ));
    }

    for profile_id in &manifest.supported_profiles {
        if !profiles.contains(profile_id.as_str()) {
            reasons.push(negotiation_rejection(
                ExtensionNegotiationRejectionCode::UnsupportedProfile,
                format!(
                    "profile {} is not part of {}",
                    profile_id, package.package_id
                ),
            ));
        }
    }
    for component_id in &manifest.compatibility.supported_component_ids {
        if !components.contains(component_id.as_str()) {
            reasons.push(negotiation_rejection(
                ExtensionNegotiationRejectionCode::UnsupportedComponent,
                format!(
                    "component {} is not part of {}",
                    component_id, package.package_id
                ),
            ));
        }
    }
    if !manifest
        .compatibility
        .supported_component_ids
        .iter()
        .any(|component_id| {
            point
                .official_component_ids
                .iter()
                .any(|official| official == component_id)
        })
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::UnsupportedComponent,
            format!(
                "extension {} does not target an official component for point {}",
                manifest.extension_id, point.id
            ),
        ));
    }

    if !point
        .allowed_isolations
        .contains(&manifest.runtime.isolation)
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::UnsupportedIsolation,
            format!(
                "extension point {} does not allow {:?} isolation",
                point.id, manifest.runtime.isolation
            ),
        ));
    }
    if !point
        .allowed_evidence_modes
        .contains(&manifest.runtime.evidence_mode)
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::UnsupportedEvidenceMode,
            format!(
                "extension point {} does not allow {:?} evidence mode",
                point.id, manifest.runtime.evidence_mode
            ),
        ));
    }
    for privilege in &manifest.runtime.allowed_privileges {
        if !point.allowed_privileges.contains(privilege) {
            reasons.push(negotiation_rejection(
                ExtensionNegotiationRejectionCode::UnsupportedPrivilege,
                format!(
                    "extension point {} does not allow {:?}",
                    point.id, privilege
                ),
            ));
        }
    }

    if point.policy_activation_required && !manifest.runtime.requires_local_policy_activation {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::LocalPolicyActivationRequired,
            format!(
                "extension point {} requires local policy activation",
                point.id
            ),
        ));
    }
    if manifest.runtime.evidence_mode != ExtensionEvidenceMode::None
        && !manifest.runtime.requires_subject_binding
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::MissingSubjectBinding,
            format!(
                "extension {} omitted subject binding",
                manifest.extension_id
            ),
        ));
    }
    if manifest.runtime.evidence_mode != ExtensionEvidenceMode::None
        && !manifest.runtime.requires_signer_verification
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::MissingSignerVerification,
            format!(
                "extension {} omitted signer verification",
                manifest.extension_id
            ),
        ));
    }
    if manifest.runtime.evidence_mode != ExtensionEvidenceMode::None
        && !manifest.runtime.requires_freshness_check
    {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::MissingFreshnessCheck,
            format!(
                "extension {} omitted freshness checks",
                manifest.extension_id
            ),
        ));
    }
    if manifest.runtime.allows_truth_mutation {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::TruthMutationNotAllowed,
            format!("extension {} claims truth mutation", manifest.extension_id),
        ));
    }
    if manifest.runtime.allows_trust_widening {
        reasons.push(negotiation_rejection(
            ExtensionNegotiationRejectionCode::TrustWideningNotAllowed,
            format!("extension {} claims trust widening", manifest.extension_id),
        ));
    }

    ExtensionNegotiationReport {
        schema: CHIO_EXTENSION_NEGOTIATION_SCHEMA.to_string(),
        official_stack_package_id: package.package_id.clone(),
        extension_id: manifest.extension_id.clone(),
        extension_point_id: manifest.extension_point_id.clone(),
        outcome: if reasons.is_empty() {
            ExtensionNegotiationOutcome::Accepted
        } else {
            ExtensionNegotiationOutcome::Rejected
        },
        reasons,
    }
}

fn negotiation_rejection(
    code: ExtensionNegotiationRejectionCode,
    detail: impl Into<String>,
) -> ExtensionNegotiationRejection {
    ExtensionNegotiationRejection {
        code,
        detail: detail.into(),
    }
}