harn-vm 0.10.132

Async bytecode virtual machine for the Harn programming language
Documentation
pub(super) fn mutation_status(result: &serde_json::Value) -> &'static str {
    let status = fact(result, "mutation_status").and_then(serde_json::Value::as_str);
    match status {
        Some("applied") => crate::agent_events::ToolMutationStatus::Applied.as_str(),
        Some("unchanged") => crate::agent_events::ToolMutationStatus::Unchanged.as_str(),
        Some("not_applied") => crate::agent_events::ToolMutationStatus::NotApplied.as_str(),
        _ => crate::agent_events::ToolMutationStatus::Unknown.as_str(),
    }
}

pub(super) fn changed_paths(result: &serde_json::Value) -> Option<Vec<&str>> {
    let paths = fact(result, "changed_paths")
        .and_then(serde_json::Value::as_array)?
        .iter()
        .filter_map(serde_json::Value::as_str)
        .filter(|path| !path.trim().is_empty())
        .collect();
    Some(paths)
}

pub(super) fn data(
    result: &serde_json::Value,
) -> Option<&serde_json::Map<String, serde_json::Value>> {
    if result.get("schema").and_then(serde_json::Value::as_str)
        != Some("harn.agent_tool_handler_result.v1")
    {
        return None;
    }
    result.get("data")?.as_object()
}

pub(super) struct FailureProjection {
    pub category: Option<&'static str>,
    pub error: Option<String>,
}

pub(super) fn failure_projection(
    raw_result: &serde_json::Value,
    declared_failure: Option<&'static str>,
    rendered: &str,
    hook_denial: Option<&crate::orchestration::PostToolDenial>,
) -> FailureProjection {
    let denied = super::agent_tools::is_denied_tool_result(raw_result);
    let category = if hook_denial.is_some() || denied {
        Some("tool_rejected")
    } else {
        declared_failure.or_else(|| super::agent_tools::ok_result_failure_category(raw_result))
    };
    let error = hook_denial
        .map(|denial| denial.message.clone())
        .or_else(|| category.is_some().then(|| rendered.to_string()));
    FailureProjection { category, error }
}

pub(super) fn report_post_hook_truncation(
    dropped_bytes: usize,
    tool_name: &str,
    session_id: &str,
) -> bool {
    if dropped_bytes == 0 {
        return false;
    }
    crate::boundary::BoundaryFailure::new(
        crate::boundary::BoundaryId::PostToolOutput,
        crate::boundary::BoundaryFailureKind::Truncated,
        format!("PostToolUse hooks truncated output from tool {tool_name}"),
    )
    .with_dropped_bytes(dropped_bytes)
    .in_session(session_id)
    .report();
    true
}

fn fact<'a>(result: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
    result.get(key).or_else(|| data(result)?.get(key))
}

#[cfg(test)]
mod tests {
    use super::{changed_paths, data, mutation_status};

    #[test]
    fn lifts_only_declared_mutation_outcomes() {
        assert_eq!(
            mutation_status(&serde_json::json!({"mutation_status": "applied"})),
            "applied"
        );
        assert_eq!(
            mutation_status(&serde_json::json!({"mutation_status": "unchanged"})),
            "unchanged"
        );
        assert_eq!(
            mutation_status(&serde_json::json!({"mutation_status": "not_applied"})),
            "not_applied"
        );
        assert_eq!(
            mutation_status(&serde_json::json!({
                "schema": "harn.agent_tool_handler_result.v1",
                "text": "Edited src/lib.rs",
                "data": {"mutation_status": "applied"}
            })),
            "applied"
        );
        assert_eq!(
            mutation_status(&serde_json::json!({
                "schema": "harn.agent_tool_handler_result.v1",
                "mutation_status": "not_applied",
                "data": {"mutation_status": "applied"}
            })),
            "not_applied"
        );
        for result in [
            serde_json::json!({}),
            serde_json::json!({"mutation_status": "maybe"}),
            serde_json::json!({"mutation_status": 1}),
            serde_json::json!({"mutationStatus": "applied"}),
            serde_json::json!({
                "schema": "another.result.v1",
                "data": {"mutation_status": "applied"}
            }),
        ] {
            assert_eq!(mutation_status(&result), "unknown");
        }
    }

    #[test]
    fn lifts_only_nonempty_string_paths() {
        let result = serde_json::json!({
            "changed_paths": ["src/lib.rs", "", 7, "tests/lib.rs"]
        });
        assert_eq!(
            changed_paths(&result),
            Some(vec!["src/lib.rs", "tests/lib.rs"])
        );
        assert_eq!(
            changed_paths(&serde_json::json!({
                "schema": "harn.agent_tool_handler_result.v1",
                "text": "Edited src/lib.rs",
                "data": {"changed_paths": ["src/lib.rs"]}
            })),
            Some(vec!["src/lib.rs"])
        );
        assert!(changed_paths(&serde_json::json!({
            "changed_paths": "src/lib.rs"
        }))
        .is_none());
    }

    #[test]
    fn exposes_only_declared_handler_data_without_key_filtering() {
        let result = serde_json::json!({
            "schema": "harn.agent_tool_handler_result.v1",
            "text": "Command wording is deliberately not machine-readable.",
            "data": {
                "command_status": "succeeded",
                "run_outcome": {"exit_code": 0}
            }
        });
        assert_eq!(
            data(&result),
            result["data"].as_object(),
            "the producer-owned data map is projected whole"
        );
        for result in [
            serde_json::json!({"data": {"run_outcome": {"exit_code": 0}}}),
            serde_json::json!({
                "schema": "another.result.v1",
                "data": {"run_outcome": {"exit_code": 0}}
            }),
            serde_json::json!({
                "schema": "harn.agent_tool_handler_result.v1",
                "data": "not-a-map"
            }),
        ] {
            assert!(data(&result).is_none());
        }
    }
}