// uni-db plugin framework — Component Model worlds.
//
// One WIT package, multiple worlds. Each world describes one plugin
// kind's typed contract. Per the proposal §6, every plugin world
// shares:
//
// - The `manifest` and `register` exports — JSON control surfaces
// read at load time so the host can negotiate capabilities.
// - An optional `host-log` import — always-available, used for
// plugin-side tracing.
// - Arrow IPC bytes as the columnar wire format. The IPC payload
// itself is opaque to WIT — we ship `list<u8>` across the boundary.
//
// M6b scope (the M6 closure): scalar, aggregate, procedure.
// The remaining surfaces (storage/index/algo/hook/connector/...)
// follow in later milestones as their host-side traits stabilize.
package uni:plugin@0.1.0;
interface types {
/// Per-call error returned by a plugin's work function.
///
/// `code` is plugin-defined; framework codes occupy 0..=0xFF.
/// `message` is human-readable. `retryable` is a hint to the
/// host call-site dispatcher.
record fn-error {
code: u32,
message: string,
retryable: bool,
}
}
/// Always-available host imports.
///
/// `host-log` is the only universally-allowed host call. Other host
/// functions (filesystem, network, KMS, …) live in capability-gated
/// interfaces under `uni:plugin/host-fs`, `uni:plugin/host-net`, etc.
/// Those interfaces are NOT declared here — the proposal calls for
/// them to be added to the Linker by the host only when the matching
/// capability has been granted, so a plugin that imports an absent
/// interface fails at link time.
interface host-log {
log: func(level: string, message: string);
}
/// Capability-gated network egress.
///
/// Added to the `Linker` by the host **only** when the plugin is granted
/// `Capability::Network`; a plugin importing this interface without the grant
/// fails at link time (the structural half of capability enforcement). The
/// host additionally enforces the granted URL allow-list at call time, before
/// any socket is opened, and clamps `timeout-ms` / `max-bytes` to the granted
/// ceiling. The host's active W3C trace context is injected into the outbound
/// request automatically (see `host-trace-context`).
interface host-net {
use types.{fn-error};
/// HTTP response: status code plus the (size-capped) body bytes.
record http-response {
status: u16,
body: list<u8>,
}
/// HTTP GET. `timeout-ms` (0 = host default) and `max-bytes` (0 = host
/// cap) are upper bounds the host clamps to its granted ceiling.
http-get: func(url: string, timeout-ms: u64, max-bytes: u32)
-> result<http-response, fn-error>;
/// HTTP POST of `body`. Bounds behave as for `http-get`.
http-post: func(url: string, body: list<u8>, timeout-ms: u64, max-bytes: u32)
-> result<http-response, fn-error>;
}
/// Host trace-context propagation.
///
/// Always added to the `Linker` (no capability required — it leaks nothing).
/// Returns the host's active W3C `traceparent`, or `none` when no trace is
/// active (e.g. the host's `otel` feature is off — no fabricated ids).
interface host-trace-context {
get-traceparent: func() -> option<string>;
}
/// Scalar plugin world.
///
/// Implements one or more Cypher scalar functions. The wire shape:
///
/// - `manifest` returns the plugin's canonical JSON manifest
/// (id, version, declared capabilities).
/// - `register` returns the JSON `RegistrationManifest`
/// enumerating every qname the plugin provides plus its
/// wire-level signature.
/// - `invoke-scalar` takes `(qname, ipc-bytes)` and returns
/// Arrow IPC stream bytes (one batch with one output column).
world scalar-plugin {
use types.{fn-error};
import host-log;
export manifest: func() -> string;
export register: func() -> string;
export invoke-scalar: func(qname: string, ipc-bytes: list<u8>)
-> result<list<u8>, fn-error>;
}
/// Aggregate plugin world.
///
/// Implements one or more Cypher aggregate functions. State is opaque
/// bytes; the host carries it as an Arrow `Binary` value between
/// `agg-update` / `agg-merge` / `agg-evaluate` calls. The plugin owns
/// the state's interpretation.
///
/// The aggregate ABI mirrors `uni_plugin_extism::adapter_aggregate`
/// exactly so the cross-ABI parity test produces byte-identical state
/// transitions for the same input.
world aggregate-plugin {
use types.{fn-error};
import host-log;
export manifest: func() -> string;
export register: func() -> string;
/// Initial state for a fresh per-group accumulator.
export agg-new: func(qname: string) -> result<list<u8>, fn-error>;
/// Ingest `values-ipc` into the accumulator at `state`. Returns
/// the new state bytes.
export agg-update: func(qname: string, state: list<u8>, values-ipc: list<u8>)
-> result<list<u8>, fn-error>;
/// Merge `other-states-ipc` (an Arrow IPC stream with one
/// `Binary` column) into the accumulator at `state`.
export agg-merge: func(qname: string, state: list<u8>, other-states-ipc: list<u8>)
-> result<list<u8>, fn-error>;
/// Produce the final aggregate value as a 1-row × 1-col Arrow
/// IPC stream.
export agg-evaluate: func(qname: string, state: list<u8>)
-> result<list<u8>, fn-error>;
}
/// Procedure plugin world.
///
/// Implements one or more Cypher procedures. The plugin receives a
/// 1-row argument batch and produces zero or more `yields`-shaped
/// batches collected eagerly by the host (true streaming via
/// host-yield is a future ABI extension).
world procedure-plugin {
use types.{fn-error};
import host-log;
export manifest: func() -> string;
export register: func() -> string;
export invoke-procedure: func(qname: string, args-ipc: list<u8>)
-> result<list<u8>, fn-error>;
}