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 in-engine dispatch seam. Every backend (builtin, expression, wasm
//! container, llm) implements [`TransformBackend`]; the standalone
//! `fv-compute` library is what the pipeline runner, the derived-property materializer, the
//! Action runtime, or a REPL embeds to run `transform X@v over this Arrow batch`.
//!
//! Scalable-pipeline shape (the whole point): the seam is **batch-vectorized**
//! (whole `RecordBatch`es, never row-at-a-time) and **`Send + Sync`**, so DataFusion can call a
//! loaded [`Compute`] per partition across all cores; a backend that has per-instance cost (the
//! wasm `Store` pool) amortizes it behind [`TransformBackend::load`], which is invoked once and
//! whose [`Compute`] is then called per batch.

use crate::manifest::{ImplKind, TransformManifest};
use arrow::array::RecordBatch;
use std::path::Path;

#[derive(Debug, thiserror::Error)]
pub enum ComputeError {
    /// The backend could not load/compile the transform (missing artifact, bad module, …).
    #[error("failed to load transform `{id}`: {msg}")]
    Load { id: String, msg: String },
    /// The input batch(es) did not match the declared signature.
    #[error("schema mismatch in `{id}`: {msg}")]
    Schema { id: String, msg: String },
    /// The transform itself failed (guest error, timeout, fail-closed).
    #[error("transform `{id}` failed: {msg}")]
    Run { id: String, msg: String },
    /// No backend is registered for this impl kind.
    #[error("no backend registered for impl `{kind:?}`")]
    NoBackend { kind: ImplKind },
}

/// A loaded, ready-to-invoke transform. Cheap to call per batch (any expensive setup happened in
/// [`TransformBackend::load`]). `Send + Sync` so it can be shared across partition workers.
pub trait Compute: Send + Sync {
    /// The manifest this compute was loaded from.
    fn manifest(&self) -> &TransformManifest;

    /// Transform one set of input batches into one output batch (batch-vectorized). The number of
    /// input batches matches `manifest().inputs`. Returns [`ComputeError`] to fail closed —
    /// governance wraps this; the compute never touches the write path.
    fn run(&self, inputs: &[RecordBatch]) -> Result<RecordBatch, ComputeError>;
}

/// A pluggable backend for one [`ImplKind`]. Implemented by the wasm/container/expression/builtin
/// backends. `load` does the expensive work once (compile the wasm module, parse
/// the expression, resolve the builtin); the returned [`Compute`] is the hot per-batch path.
pub trait TransformBackend: Send + Sync {
    /// Which impl kind this backend serves.
    fn kind(&self) -> ImplKind;

    /// Load a transform from its (validated) manifest and directory root into a callable [`Compute`].
    fn load(&self, manifest: &TransformManifest, root: &Path) -> Result<Box<dyn Compute>, ComputeError>;
}