car-ffi-common 0.32.1

Shared logic for FFI bindings (NAPI, PyO3) — JSON wrappers for verify, multi-agent, scheduler
//! JSON wrapper for the localized-vs-global maintenance decision
//! (arXiv 2606.24775).
//!
//! Stateless helper, binding-only — see
//! `docs/proposals/agent-native-memory-diagnostic.md`. Applies the paper's
//! finding that localized maintenance is more cost-efficient than global
//! reorganization: given how much is dirty, the store size, the per-strategy
//! costs, and the value of a global reorg's structural gain, decide whether to
//! patch locally or reorganize the whole store.

use car_memgine::maintenance::{decide_maintenance, MaintenanceInput};

/// Decide localized vs. global maintenance. `input_json` is a [`MaintenanceInput`]
/// (`{ dirty_regions, total_regions, localized_cost_per_region,
/// global_cost_per_region, global_structural_gain, gain_value }`; omitted fields
/// default to 0). Returns the [`car_memgine::maintenance::MaintenanceDecision`]
/// as JSON — `{ strategy: "no_op" | "localized" | "global", localized_cost,
/// global_cost, global_extra_value, global_net_advantage, rationale }`.
pub fn decide(input_json: &str) -> Result<String, String> {
    let input: MaintenanceInput = crate::from_json("input", input_json)?;
    let decision = decide_maintenance(&input);
    crate::to_json(&decision)
}

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

    #[test]
    fn large_store_small_dirty_is_localized() {
        let input = r#"{
            "dirty_regions": 5, "total_regions": 1000,
            "localized_cost_per_region": 1.0, "global_cost_per_region": 1.0,
            "global_structural_gain": 0.1, "gain_value": 10.0
        }"#;
        let out = decide(input).unwrap();
        let v: Value = serde_json::from_str(&out).unwrap();
        assert_eq!(v["strategy"], "localized");
    }

    #[test]
    fn empty_input_is_noop() {
        let out = decide("{}").unwrap();
        let v: Value = serde_json::from_str(&out).unwrap();
        assert_eq!(v["strategy"], "no_op");
    }

    #[test]
    fn invalid_json_errors() {
        assert!(decide("not json").is_err());
    }
}