greentic-x 0.4.13

Greentic-X CLI for catalog-driven composition, scaffolding, validation, and simulation.
Documentation
use serde_json::Value;

use crate::{
    PackCapabilityMapping, PackInputDocument, PackProviderHint, PackTemplateSelection,
    ResolvedSolutionIntent,
};

const SCHEMA_VERSION: &str = "1.0.0";

pub(crate) fn map_solution_intent_to_pack_input(
    intent: &ResolvedSolutionIntent,
    solution_intent_ref: &str,
) -> PackInputDocument {
    let provider_hints = intent
        .provider_presets
        .iter()
        .map(|preset| PackProviderHint {
            entry_id: preset
                .get("entry_id")
                .and_then(Value::as_str)
                .map(ToOwned::to_owned),
            display_name: preset
                .get("display_name")
                .and_then(Value::as_str)
                .map(ToOwned::to_owned),
            provider_refs: preset
                .get("provider_refs")
                .and_then(Value::as_array)
                .map(|items| {
                    items
                        .iter()
                        .filter_map(Value::as_str)
                        .map(ToOwned::to_owned)
                        .collect::<Vec<_>>()
                })
                .unwrap_or_default(),
        })
        .collect::<Vec<_>>();

    let provider_refs = provider_hints
        .iter()
        .flat_map(|hint| hint.provider_refs.iter().cloned())
        .collect::<std::collections::BTreeSet<_>>()
        .into_iter()
        .collect::<Vec<_>>();

    let template_selection = PackTemplateSelection {
        entry_id: intent
            .template
            .get("entry_id")
            .and_then(Value::as_str)
            .map(ToOwned::to_owned),
        display_name: intent
            .template
            .get("display_name")
            .and_then(Value::as_str)
            .map(ToOwned::to_owned),
        assistant_template_ref: intent
            .template
            .get("assistant_template_ref")
            .and_then(Value::as_str)
            .map(ToOwned::to_owned),
        domain_template_ref: intent
            .template
            .get("domain_template_ref")
            .and_then(Value::as_str)
            .map(ToOwned::to_owned),
    };

    let greentic_cap_mapping = intent
        .required_capabilities
        .iter()
        .map(|requirement| PackCapabilityMapping {
            gx_requirement: requirement.clone(),
            greentic_cap_concept: "required capability offer".to_owned(),
            status: "partial_compatibility_mapping".to_owned(),
        })
        .collect::<Vec<_>>();

    PackInputDocument {
        schema_id: "gx.pack.input".to_owned(),
        schema_version: SCHEMA_VERSION.to_owned(),
        solution_id: intent.solution_id.clone(),
        solution_intent_ref: solution_intent_ref.to_owned(),
        provider_refs,
        required_capability_offers: intent.required_capabilities.clone(),
        required_contracts: intent.required_contracts.clone(),
        suggested_flows: intent.suggested_flows.clone(),
        provider_hints,
        template_selection,
        defaults: intent.defaults.clone(),
        unresolved_downstream_work: vec![
            "Choose or scaffold the target pack root and pack identifiers.".to_owned(),
            "Translate pack input into greentic-pack wizard answers or pack.yaml state."
                .to_owned(),
            "Resolve capability offers and extension/component authoring details through greentic-pack."
                .to_owned(),
            "Run pack doctor, resolve, build, sign, and any manifest synchronization in greentic-pack."
                .to_owned(),
        ],
        greentic_cap_mapping,
        notes: vec![
            "This document is a compatibility input for greentic-pack, not a pack manifest."
                .to_owned(),
            "Capability requirements are carried as GX requirements mapped onto greentic-cap-style required capability offers."
                .to_owned(),
        ],
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn maps_solution_intent_into_pack_input_document() {
        let intent = ResolvedSolutionIntent {
            schema_id: "gx.solution.intent".to_owned(),
            schema_version: "1.0.0".to_owned(),
            solution_id: "demo".to_owned(),
            solution_name: "Demo".to_owned(),
            description: String::new(),
            output_dir: "dist".to_owned(),
            solution_kind: "assistant".to_owned(),
            template: json!({
                "entry_id": "assistant.demo",
                "display_name": "Demo Template",
                "assistant_template_ref": "oci://example/template:latest"
            }),
            provider_presets: vec![json!({
                "entry_id": "builtin.teams",
                "display_name": "Teams",
                "provider_refs": ["oci://example/provider:latest"]
            })],
            overlay: None,
            catalog_refs: Vec::new(),
            catalog_sources: Vec::new(),
            required_capabilities: vec!["messaging.send".to_owned()],
            required_contracts: vec!["gx.customer.case".to_owned()],
            suggested_flows: vec!["customer.triage".to_owned()],
            defaults: json!({"provider_selection": "teams"}),
            notes: Vec::new(),
        };

        let pack_input = map_solution_intent_to_pack_input(&intent, "dist/demo.solution.json");

        assert_eq!(pack_input.schema_id, "gx.pack.input");
        assert_eq!(
            pack_input.provider_refs,
            vec!["oci://example/provider:latest".to_owned()]
        );
        assert_eq!(
            pack_input.required_capability_offers,
            vec!["messaging.send".to_owned()]
        );
        assert_eq!(
            pack_input.required_contracts,
            vec!["gx.customer.case".to_owned()]
        );
        assert_eq!(
            pack_input.suggested_flows,
            vec!["customer.triage".to_owned()]
        );
        assert_eq!(
            pack_input.template_selection.entry_id.as_deref(),
            Some("assistant.demo")
        );
        assert_eq!(pack_input.greentic_cap_mapping.len(), 1);
        assert!(!pack_input.unresolved_downstream_work.is_empty());
    }
}