assay-cli 3.19.0

CLI for Assay
use super::reports::{SchemaKind, SchemaMetadata};
use anyhow::{bail, Context, Result};
use serde_json::Value;

macro_rules! include_packaged_schema {
    ($relative_path:literal) => {
        include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/receipt-schemas/",
            $relative_path
        ))
    };
}

const PROMPTFOO_RECEIPT_SCHEMA: &str =
    include_packaged_schema!("receipts/promptfoo.assertion-component.v1.schema.json");
const OPENFEATURE_RECEIPT_SCHEMA: &str =
    include_packaged_schema!("receipts/openfeature.evaluation-details.v1.schema.json");
const CYCLONEDX_RECEIPT_SCHEMA: &str =
    include_packaged_schema!("receipts/cyclonedx.mlbom-model-component.v1.schema.json");
const MASTRA_RECEIPT_SCHEMA: &str =
    include_packaged_schema!("receipts/mastra.score-event.v1.schema.json");
const PYDANTIC_RECEIPT_SCHEMA: &str =
    include_packaged_schema!("receipts/pydantic.case-result.v1.schema.json");
const LIVEKIT_RECEIPT_SCHEMA: &str =
    include_packaged_schema!("receipts/livekit.tool-action.v1.schema.json");
const PROMPTFOO_INPUT_SCHEMA: &str =
    include_packaged_schema!("inputs/promptfoo-cli-jsonl-component-result.v1.schema.json");
const OPENFEATURE_INPUT_SCHEMA: &str =
    include_packaged_schema!("inputs/openfeature-evaluation-details-export.v1.schema.json");
const CYCLONEDX_INPUT_SCHEMA: &str =
    include_packaged_schema!("inputs/cyclonedx-mlbom-model-component-input.v1.schema.json");
const MASTRA_INPUT_SCHEMA: &str =
    include_packaged_schema!("inputs/mastra-score-event-export.v1.schema.json");
const PYDANTIC_INPUT_SCHEMA: &str =
    include_packaged_schema!("inputs/pydantic-case-result-export.v1.schema.json");
const LIVEKIT_INPUT_SCHEMA: &str =
    include_packaged_schema!("inputs/livekit-function-tools-executed-export.v1.schema.json");

#[derive(Clone, Copy)]
pub(super) struct SchemaDescriptor {
    pub(super) name: &'static str,
    aliases: &'static [&'static str],
    pub(super) kind: SchemaKind,
    status: &'static str,
    family: &'static str,
    source_path: &'static str,
    description: &'static str,
    trust_basis_claim: Option<&'static str>,
    importer_only: bool,
    schema_json: &'static str,
}

impl SchemaDescriptor {
    pub(super) fn json_schema_value(&self) -> Result<Value> {
        serde_json::from_str(self.schema_json)
            .with_context(|| format!("failed to parse embedded schema {}", self.name))
    }

    fn json_schema_id(&self) -> Result<String> {
        Ok(self
            .json_schema_value()?
            .get("$id")
            .and_then(Value::as_str)
            .unwrap_or(self.name)
            .to_string())
    }

    fn matches(&self, needle: &str) -> Result<bool> {
        if self.name == needle || self.source_path == needle || self.aliases.contains(&needle) {
            return Ok(true);
        }
        Ok(self.json_schema_id()? == needle)
    }
}

pub(super) const SCHEMAS: &[SchemaDescriptor] = &[
    SchemaDescriptor {
        name: "promptfoo.assertion-component.v1",
        aliases: &["assay.receipt.promptfoo.assertion-component.v1"],
        kind: SchemaKind::Receipt,
        status: "stable",
        family: "external_eval_receipts",
        source_path: "docs/reference/receipt-schemas/receipts/promptfoo.assertion-component.v1.schema.json",
        description: "Assay receipt payload for one selected Promptfoo assertion component result.",
        trust_basis_claim: Some("external_eval_receipt_boundary_visible"),
        importer_only: false,
        schema_json: PROMPTFOO_RECEIPT_SCHEMA,
    },
    SchemaDescriptor {
        name: "openfeature.evaluation-details.v1",
        aliases: &["assay.receipt.openfeature.evaluation_details.v1"],
        kind: SchemaKind::Receipt,
        status: "stable",
        family: "external_decision_receipts",
        source_path: "docs/reference/receipt-schemas/receipts/openfeature.evaluation-details.v1.schema.json",
        description: "Assay receipt payload for one bounded OpenFeature boolean EvaluationDetails decision.",
        trust_basis_claim: Some("external_decision_receipt_boundary_visible"),
        importer_only: false,
        schema_json: OPENFEATURE_RECEIPT_SCHEMA,
    },
    SchemaDescriptor {
        name: "cyclonedx.mlbom-model-component.v1",
        aliases: &["assay.receipt.cyclonedx.mlbom-model-component.v1"],
        kind: SchemaKind::Receipt,
        status: "stable",
        family: "external_inventory_receipts",
        source_path: "docs/reference/receipt-schemas/receipts/cyclonedx.mlbom-model-component.v1.schema.json",
        description: "Assay receipt payload for one selected CycloneDX ML-BOM machine-learning-model component.",
        trust_basis_claim: Some("external_inventory_receipt_boundary_visible"),
        importer_only: false,
        schema_json: CYCLONEDX_RECEIPT_SCHEMA,
    },
    SchemaDescriptor {
        name: "mastra.score-event.v1",
        aliases: &["assay.receipt.mastra.score_event.v1"],
        kind: SchemaKind::Receipt,
        status: "experimental",
        family: "score_receipts",
        source_path: "docs/reference/receipt-schemas/receipts/mastra.score-event.v1.schema.json",
        description: "Assay receipt payload for one bounded Mastra ScoreEvent-derived score artifact.",
        trust_basis_claim: None,
        importer_only: true,
        schema_json: MASTRA_RECEIPT_SCHEMA,
    },
    SchemaDescriptor {
        name: "pydantic.case-result.v1",
        aliases: &["assay.receipt.pydantic.case_result.v1"],
        kind: SchemaKind::Receipt,
        status: "experimental",
        family: "case_result_receipts",
        source_path: "docs/reference/receipt-schemas/receipts/pydantic.case-result.v1.schema.json",
        description: "Assay receipt payload for one bounded Pydantic Evals reduced case-result artifact.",
        trust_basis_claim: None,
        importer_only: true,
        schema_json: PYDANTIC_RECEIPT_SCHEMA,
    },
    SchemaDescriptor {
        name: "livekit.tool-action.v1",
        aliases: &["assay.receipt.livekit.tool-action.v1"],
        kind: SchemaKind::Receipt,
        status: "experimental",
        family: "acted_receipts_candidate",
        source_path: "docs/reference/receipt-schemas/receipts/livekit.tool-action.v1.schema.json",
        description: "Assay receipt payload for one bounded LiveKit function tool action artifact.",
        trust_basis_claim: None,
        importer_only: true,
        schema_json: LIVEKIT_RECEIPT_SCHEMA,
    },
    SchemaDescriptor {
        name: "promptfoo-cli-jsonl-component-result.v1",
        aliases: &[],
        kind: SchemaKind::Input,
        status: "stable",
        family: "external_eval_receipts",
        source_path: "docs/reference/receipt-schemas/inputs/promptfoo-cli-jsonl-component-result.v1.schema.json",
        description: "Supported Promptfoo CLI JSONL row shape containing assertion component results.",
        trust_basis_claim: Some("external_eval_receipt_boundary_visible"),
        importer_only: false,
        schema_json: PROMPTFOO_INPUT_SCHEMA,
    },
    SchemaDescriptor {
        name: "openfeature-evaluation-details-export.v1",
        aliases: &[],
        kind: SchemaKind::Input,
        status: "stable",
        family: "external_decision_receipts",
        source_path: "docs/reference/receipt-schemas/inputs/openfeature-evaluation-details-export.v1.schema.json",
        description: "Supported reduced OpenFeature boolean EvaluationDetails JSONL row shape.",
        trust_basis_claim: Some("external_decision_receipt_boundary_visible"),
        importer_only: false,
        schema_json: OPENFEATURE_INPUT_SCHEMA,
    },
    SchemaDescriptor {
        name: "cyclonedx-mlbom-model-component-input.v1",
        aliases: &[],
        kind: SchemaKind::Input,
        status: "stable",
        family: "external_inventory_receipts",
        source_path: "docs/reference/receipt-schemas/inputs/cyclonedx-mlbom-model-component-input.v1.schema.json",
        description: "Supported CycloneDX ML-BOM input shape for selecting one machine-learning-model component.",
        trust_basis_claim: Some("external_inventory_receipt_boundary_visible"),
        importer_only: false,
        schema_json: CYCLONEDX_INPUT_SCHEMA,
    },
    SchemaDescriptor {
        name: "mastra-score-event-export.v1",
        aliases: &[],
        kind: SchemaKind::Input,
        status: "experimental",
        family: "score_receipts",
        source_path: "docs/reference/receipt-schemas/inputs/mastra-score-event-export.v1.schema.json",
        description: "Supported reduced Mastra ScoreEvent JSONL row shape.",
        trust_basis_claim: None,
        importer_only: true,
        schema_json: MASTRA_INPUT_SCHEMA,
    },
    SchemaDescriptor {
        name: "pydantic-case-result-export.v1",
        aliases: &[],
        kind: SchemaKind::Input,
        status: "experimental",
        family: "case_result_receipts",
        source_path: "docs/reference/receipt-schemas/inputs/pydantic-case-result-export.v1.schema.json",
        description: "Supported reduced Pydantic Evals case-result JSONL row shape.",
        trust_basis_claim: None,
        importer_only: true,
        schema_json: PYDANTIC_INPUT_SCHEMA,
    },
    SchemaDescriptor {
        name: "livekit-function-tools-executed-export.v1",
        aliases: &[],
        kind: SchemaKind::Input,
        status: "experimental",
        family: "acted_receipts_candidate",
        source_path: "docs/reference/receipt-schemas/inputs/livekit-function-tools-executed-export.v1.schema.json",
        description: "Supported reduced LiveKit FunctionToolsExecutedEvent artifact shape.",
        trust_basis_claim: None,
        importer_only: true,
        schema_json: LIVEKIT_INPUT_SCHEMA,
    },
];

pub(super) fn find_schema(raw: &str) -> Result<&'static SchemaDescriptor> {
    let needle = raw.trim();
    if needle.is_empty() {
        bail!("schema name is empty");
    }

    for descriptor in SCHEMAS {
        if descriptor.matches(needle)? {
            return Ok(descriptor);
        }
    }

    bail!("unknown schema {needle:?}; run `assay evidence schema list` for supported schemas");
}

pub(super) fn schema_metadata(descriptor: &SchemaDescriptor) -> Result<SchemaMetadata> {
    Ok(SchemaMetadata {
        name: descriptor.name.to_string(),
        kind: descriptor.kind,
        status: descriptor.status.to_string(),
        family: descriptor.family.to_string(),
        json_schema_id: descriptor.json_schema_id()?,
        source_path: descriptor.source_path.to_string(),
        description: descriptor.description.to_string(),
        trust_basis_claim: descriptor.trust_basis_claim.map(str::to_string),
        importer_only: descriptor.importer_only,
        aliases: descriptor
            .aliases
            .iter()
            .map(|alias| (*alias).to_string())
            .collect(),
    })
}