assay-core 3.9.1

High-performance evaluation framework for LLM agents (Core)
Documentation
use super::super::{McpPolicy, PolicyDecision, PolicyMatchMetadata};
use super::diagnostics::format_deny_contract;
use super::matcher::{
    classify_match_basis, has_allowlist, is_allowed, is_denied, matched_allow_classes,
    matched_deny_classes, matched_rule_name,
};
use std::collections::BTreeSet;

pub(in crate::mcp::policy) fn deny_match_decision(
    policy: &McpPolicy,
    tool_name: &str,
    tool_classes: &BTreeSet<String>,
    metadata: &mut PolicyMatchMetadata,
) -> Option<PolicyDecision> {
    let deny_name_match = is_denied(policy, tool_name);
    let deny_class_matches = matched_deny_classes(policy, tool_classes);
    if !deny_name_match && deny_class_matches.is_empty() {
        return None;
    }

    metadata.matched_tool_classes = deny_class_matches.clone();
    metadata.match_basis = classify_match_basis(deny_name_match, !deny_class_matches.is_empty());
    metadata.matched_rule = Some(matched_rule_name(
        "tools.deny",
        "tools.deny_classes",
        metadata,
    ));

    let deny_reason = if deny_name_match && !deny_class_matches.is_empty() {
        "Tool is explicitly denylisted by name and class"
    } else if deny_name_match {
        "Tool is explicitly denylisted by name"
    } else {
        "Tool is explicitly denylisted by class"
    };

    Some(PolicyDecision::Deny {
        tool: tool_name.to_string(),
        code: "E_TOOL_DENIED".to_string(),
        reason: deny_reason.to_string(),
        contract: format_deny_contract(tool_name, "E_TOOL_DENIED", deny_reason),
    })
}

pub(in crate::mcp::policy) fn apply_allow_precedence(
    policy: &McpPolicy,
    tool_name: &str,
    tool_classes: &BTreeSet<String>,
    metadata: &mut PolicyMatchMetadata,
) -> Option<PolicyDecision> {
    let allow_name_match = is_allowed(policy, tool_name);
    let allow_class_matches = matched_allow_classes(policy, tool_classes);

    if has_allowlist(policy) && !allow_name_match && allow_class_matches.is_empty() {
        return Some(PolicyDecision::Deny {
            tool: tool_name.to_string(),
            code: "E_TOOL_NOT_ALLOWED".to_string(),
            reason: "Tool is not in the allowlist".to_string(),
            contract: format_deny_contract(
                tool_name,
                "E_TOOL_NOT_ALLOWED",
                "Tool is not in allowlist",
            ),
        });
    }

    if allow_name_match || !allow_class_matches.is_empty() {
        metadata.matched_tool_classes = allow_class_matches;
        metadata.match_basis =
            classify_match_basis(allow_name_match, !metadata.matched_tool_classes.is_empty());
        metadata.matched_rule = Some(matched_rule_name(
            "tools.allow",
            "tools.allow_classes",
            metadata,
        ));
    }

    None
}