harn-vm 0.10.129

Async bytecode virtual machine for the Harn programming language
Documentation
use super::*;

fn valid_catalog_json() -> JsonValue {
    json!({
        "schema_version": "harn-tools/2.0",
        "info": {"name": "inspection-suite"},
        "tools": [{
            "name": "inspect",
            "inputSchema": {"type": "object"},
            "outputSchema": {"$ref": "#/components/schemas/Result"},
            "governance": {"audiences": ["mcp"]},
            "cli": {"command": ["inspect"], "hidden": false},
            "deferLoading": false
        }],
        "components": {"schemas": {"Result": {"type": "object"}}}
    })
}

#[test]
fn catalog_rejects_schema_dialects_the_runtime_would_not_execute() {
    for phase in ["inputSchema", "outputSchema", "errorSchema"] {
        let mut value = valid_catalog_json();
        value["tools"][0][phase] = json!({
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object"
        });
        let error = serde_json::from_value::<ToolCatalog>(value)
            .expect_err("catalog must not validate one dialect and execute another");
        assert!(
            error.to_string().contains("expected Draft 2020-12"),
            "{error}"
        );
    }

    let mut component = valid_catalog_json();
    component["components"]["schemas"]["Result"] = json!({
        "$schema": "https://json-schema.org/draft/2019-09/schema",
        "type": "object"
    });
    assert!(serde_json::from_value::<ToolCatalog>(component)
        .expect_err("component dialect must match runtime semantics")
        .to_string()
        .contains("expected Draft 2020-12"));

    let mut current = valid_catalog_json();
    current["tools"][0]["outputSchema"]["$schema"] =
        json!("https://json-schema.org/draft/2020-12/schema#");
    serde_json::from_value::<ToolCatalog>(current)
        .expect("the canonical 2020-12 dialect remains accepted");
}

#[test]
fn catalog_rejects_nested_runtime_only_harn_schema_types() {
    for phase in ["inputSchema", "outputSchema", "errorSchema"] {
        let mut value = valid_catalog_json();
        value["tools"][0][phase] = json!({
            "type": "object",
            "properties": {
                "callback": {"type": "string", "x-harn-type": "closure"}
            }
        });
        let error = serde_json::from_value::<ToolCatalog>(value)
            .expect_err("runtime closure schemas cannot enter a portable catalog");
        assert!(
            error.to_string().contains(&format!(
                "{phase} contains runtime-only Harn type \"closure\""
            )),
            "{error}"
        );
    }
}

#[test]
fn catalog_rejects_invalid_or_reserved_mcp_metadata_keys() {
    for key in [
        "io.modelcontextprotocol/private",
        "com.mcp.vendor/private",
        "bad key",
    ] {
        let mut invalid = valid_catalog_json();
        invalid["tools"][0]["_meta"] =
            JsonValue::Object(serde_json::Map::from_iter([(key.to_string(), json!(true))]));
        let error = serde_json::from_value::<ToolCatalog>(invalid)
            .expect_err("invalid MCP metadata key must be rejected");
        assert!(error.to_string().contains("._meta key"), "{error}");
    }
}

#[test]
fn serde_rejects_unknown_owned_fields_and_versions() {
    assert!(serde_json::from_value::<ToolCatalog>(json!({
        "schema_version": "harn-tools/2.0", "tools": [], "extra": true
    }))
    .is_err());
    assert!(serde_json::from_value::<ToolCatalog>(json!({
        "schema_version": "harn-tools/1.0", "tools": []
    }))
    .is_err());
    assert!(serde_json::from_value::<ToolCatalog>(json!({
        "schema_version": "harn-tools/9.0", "tools": []
    }))
    .is_err());
}

#[test]
fn non_object_output_schema_and_result_preserve_the_canonical_value() {
    let mut value = valid_catalog_json();
    value["tools"][0]["outputSchema"] = json!({"type": "string"});
    let catalog: ToolCatalog = serde_json::from_value(value).expect("valid catalog");
    let entry = &catalog.tools[0];
    let projected = catalog.mcp_tool(entry).expect("MCP projection");
    let structured = catalog
        .mcp_structured_content(entry, json!("ok"))
        .expect("MCP projection")
        .expect("declared output");
    assert_eq!(
        projected["outputSchema"],
        json!({
            "type": "object",
            "properties": {"result": {"type": "string"}},
            "required": ["result"],
            "additionalProperties": false
        })
    );
    assert_eq!(structured, json!({"result": "ok"}));
    let validator = jsonschema::draft202012::new(&projected["outputSchema"])
        .expect("valid standalone output schema");
    assert!(validator.is_valid(&structured));
}

#[test]
fn mcp_object_output_detection_follows_refs_and_composition_without_guessing() {
    let referenced_object = json!({
        "$ref": "#/$defs/Result",
        "$defs": {"Result": {"type": "object", "properties": {"ok": {"type": "boolean"}}}}
    });
    assert_eq!(mcp_output_schema(&referenced_object), referenced_object);

    let all_of_object = json!({
        "allOf": [
            {"type": "object"},
            {"properties": {"ok": {"type": "boolean"}}}
        ]
    });
    assert_eq!(mcp_output_schema(&all_of_object), all_of_object);

    let mixed_union = json!({"anyOf": [{"type": "object"}, {"type": "string"}]});
    assert_eq!(
        mcp_output_schema(&mixed_union)["properties"]["result"],
        mixed_union
    );

    let properties_only = json!({"properties": {"ok": {"type": "boolean"}}});
    assert_eq!(
        mcp_output_schema(&properties_only)["properties"]["result"],
        properties_only
    );
}

#[test]
fn mcp_projects_declared_error_schema_under_reserved_metadata() {
    let mut value = valid_catalog_json();
    value["tools"][0]["errorSchema"] = json!({"$ref": "#/components/schemas/Result"});
    let catalog: ToolCatalog = serde_json::from_value(value).expect("valid catalog");
    let projected = catalog.mcp_tool(&catalog.tools[0]).expect("MCP projection");
    assert_eq!(
        projected["_meta"][HARN_MCP_TOOL_CONTRACT_META_KEY]["errorSchema"]["$ref"],
        "#/$defs/Result"
    );

    let mut reserved = valid_catalog_json();
    reserved["tools"][0]["_meta"] = json!({HARN_MCP_TOOL_CONTRACT_META_KEY: {"errorSchema": {}}});
    let error = serde_json::from_value::<ToolCatalog>(reserved)
        .expect_err("caller metadata cannot shadow Harn projection");
    assert!(
        error.to_string().contains(HARN_MCP_TOOL_CONTRACT_META_KEY),
        "{error}"
    );
}

#[test]
fn typescript_preserves_optional_and_false_compatibility() {
    let typescript = tool_catalog_typescript();
    assert!(typescript.ends_with('\n'));
    assert!(!typescript.ends_with("\n\n"));
    assert!(typescript.lines().all(|line| line == line.trim_end()));
    for expected in [
        "title?: string | null",
        "theme?: ToolIconTheme | null",
        "outputSchema?: JsonSchema202012 | null",
        "errorSchema?: JsonSchema202012 | null",
        "binding?: Readonly<Record<string, JsonValue>> | null",
        "_meta?: Readonly<Record<string, JsonValue>> | null",
        "hidden: boolean",
        "deferLoading: boolean",
    ] {
        assert!(
            typescript.contains(expected),
            "missing TS fragment {expected:?}"
        );
    }
}