fv-compute 0.2.0

The FusionVault transform/compute contract: a transform is a typed function over Arrow data declared by a manifest, discovered by a registry, and run by a pluggable backend.
Documentation
//! The catalog projection (/) — metadata only, no backend. A listing service
//! depends on `fv-compute` alone to browse transforms/models; it needs no execution runtime.

use fv_compute::{catalog_json, descriptors, model_descriptors, models_json, MemoryRoot, Registry};

fn onnx_toml() -> &'static str {
    r#"
id = "forecast"
version = "1.0.0"
impl = "onnx"
entry = "model.onnx"
[[inputs]]
columns = [{ name = "f0", type = "float64" }, { name = "f1", type = "float64" }]
[output]
columns = [{ name = "yhat", type = "float64" }]
[capabilities]
hardware = "cpu"
deterministic = true
io = false
streaming = false
[columnLineage]
yhat = ["f0", "f1"]
"#
}

fn expr_toml() -> &'static str {
    r#"
id = "doubleAmount"
version = "0.1.0"
impl = "expression"
entry = "expr.fvx"
[[inputs]]
columns = [{ name = "amount", type = "float64" }]
[output]
columns = [{ name = "amount", type = "float64" }, { name = "doubled", type = "float64" }]
[columnLineage]
doubled = ["amount"]
"#
}

fn registry() -> Registry {
    Registry::builder()
        .root(
            MemoryRoot::new("provided")
                .unit("forecast", onnx_toml())
                .unit("doubleAmount", expr_toml()),
        )
        .build()
        .unwrap()
}

#[test]
fn descriptors_project_every_unit_sorted() {
    let all = descriptors(&registry());
    assert_eq!(all.len(), 2);
    // sorted by key: doubleAmount@0.1.0 before forecast@1.0.0
    assert_eq!(all[0].key, "doubleAmount@0.1.0");
    assert_eq!(all[0].impl_kind, "expression");
    assert_eq!(all[1].key, "forecast@1.0.0");
    assert_eq!(all[1].impl_kind, "onnx");
    assert_eq!(
        all[1].inputs.iter().map(|f| f.name.as_str()).collect::<Vec<_>>(),
        vec!["f0", "f1"]
    );
    assert_eq!(all[1].outputs[0].name, "yhat");
    assert_eq!(all[1].artifact.as_deref(), Some("model.onnx"));
}

#[test]
fn model_descriptors_are_the_onnx_slice() {
    let models = model_descriptors(&registry());
    assert_eq!(models.len(), 1, "only impl=onnx units are models");
    assert_eq!(models[0].key, "forecast@1.0.0");
}

#[test]
fn json_shapes_match_the_ui_contract() {
    let reg = registry();
    let all: serde_json::Value = serde_json::from_str(&catalog_json(&reg)).unwrap();
    assert_eq!(all["transforms"].as_array().unwrap().len(), 2);

    let models: serde_json::Value = serde_json::from_str(&models_json(&reg)).unwrap();
    assert_eq!(models["models"].as_array().unwrap().len(), 1);
    assert_eq!(models["models"][0]["key"], "forecast@1.0.0");
    assert_eq!(models["models"][0]["impl"], "onnx");
    assert_eq!(models["models"][0]["inputs"][0]["type"], "float64");
}