uni-plugin 2.0.6

Plugin framework for uni-db: registry, manifest, and capability traits
Documentation
# uni-plugin changelog

All notable additions to `uni-plugin`'s public surface. Versions track
the workspace version unless an entry is annotated otherwise — the
workspace stays on `1.3.x` while individual crates publish additive
v1.4 minor bumps when their ABI grows.

## 1.9.0 — 2026-06-01 — always-on Ed25519 manifest verification + signing-payload fix (security)

Hardens signed-manifest verification. Two changes, both security-relevant:

### Changed

- **`ed25519` Cargo feature removed; signature verification is now always
  compiled.** `ed25519-dalek` and `base64` are non-optional dependencies. A
  build could previously be configured (feature off) to skip the cryptographic
  check and accept any signature whose `key_id` was merely *named* in the trust
  root — a silent-acceptance footgun. Verification is a security primitive and
  is no longer a build-time opt-out. This drops the `default`/`ed25519` features
  entirely; consumers that set `default-features = false` on `uni-plugin` (none
  in-tree) no longer disable crypto.
- **Manifest signing payload now covers the whole manifest, not just the hash
  pin.** `canonical_payload` previously signed only the blake3 hash string when
  present, so an attacker could rewrite `capabilities` / `side_effects` while
  preserving the hash and the signature still verified (capability escalation
  via manifest substitution). The payload is now a domain-separated, versioned
  (`uni-plugin-manifest-sig:v1`) canonical JSON serialization of the entire
  manifest with the `signature` field excluded. **Pre-1.9.0 signatures no longer
  verify and must be re-signed.**
- **`verify_signed_manifest` is fail-closed**: a `key_id` present in the trust
  root but without bound public-key bytes (the shape-only `TrustRoot::allow`
  path) is now rejected rather than accepted.

### Added

- **`otel` Cargo feature** (default-off) wiring real OTel trace-context
  extraction in `observability::current_trace_context()`: with the feature on
  and a `tracing-opentelemetry` layer installed, it reads the current span's
  `SpanContext`. Pulls only the lightweight `opentelemetry` API +
  `tracing-opentelemetry` (not the OTLP exporter SDK), so loaders that never
  emit OTel spans stay lean. `uni-plugin-host` enables it.
- **`TraceContext::to_traceparent()`** renders a W3C `traceparent` header value,
  and **`TraceContext` gains a `trace_flags: u8` field** and is now
  `#[non_exhaustive]` (additive; future fields like `tracestate` won't break
  downstream construction).
- **`host_services` module** with the `KmsProvider` and `HttpEgress` traits
  (+ `HttpResponse`) — the shared seam backing capability-gated `uni.kms.*` /
  `uni.http.*` host functions, so every loader binds one abstraction. Re-exported
  at the crate root.
- **`Capability::network_allows` / `kms_allows` / `secret_allows`** — call-time
  (layer-3) attenuation helpers matching a URL / key id / secret id against a
  granted `Network` / `Kms` / `Secret` allow-list (anchored `*`/`**` globs).

## 1.8.0 — 2026-05-24 — arity-overloaded procedures + `AlgoProcedure::execute_with_projection` (M5 Batch 3)

M5c.2 + M5c.3 land additive surface on the procedure registry and the
algorithm trait so the new `(graphRef, config)` 2-arg algorithm shape
can coexist with the legacy `(nodeLabels, edgeTypes, ...)` form during
the deprecation window. Adapter dispatch discriminates the two by
inspecting the JSON shape of `args[0]` (Map → V2; List → legacy);
arity-keyed registry lookup is independently available for future
overloads.

### Added

- **`PluginRegistry::procedure_with_arity(qname, arity)`** — arity-aware
  lookup; falls through to `None` if no overload matches.
- **`PluginRegistry::procedure_overloads(qname)`** — returns every
  registered overload for a qname.
- Procedure-registration now permits multiple entries under the same
  `QName` as long as each has a distinct `signature.args.len()`.
  Duplicate `(qname, arity)` pairs still error with
  `PluginError::DuplicateRegistration`.
- **`AlgoProcedure::execute_with_projection(ctx, args, projection)`**
  (`uni-algo`) — pre-built-projection entry point for V2 Cypher / Named
  graphRef variants. Default impl returns an error; the in-tree
  `GenericAlgoProcedure` overrides it for all 36 built-in algorithms.
- **`GraphProjection::from_rows(node_rows, edge_rows, weight_col, include_reverse)`**
  (`uni-algo`) — build a CSR projection from in-memory row data
  (`Vec<HashMap<String, uni_common::Value>>` shape returned by Cypher
  inner queries).
- **`ProjectionInput` enum + `parse_graph_ref`** (`uni-algo`) — V2
  graphRef map dispatcher (`Native` / `Cypher` / `Named`).
- **`ProjectionStore` + `for_storage(Arc<StorageManager>)`**
  (`uni-query`) — per-`StorageManager` cache of named projections
  backing `uni.graph.{project, drop, list, exists}`.

### Behaviour changes

- `ProjectionInput::Native.include_reverse` and `Cypher.include_reverse`
  default to **true** when omitted from `graphRef`. PageRank / Louvain /
  WCC etc. all require in-neighbors; defaulting false silently
  collapsed scores to the dangling-node baseline.
- The legacy `(nodeLabels, edgeTypes, ...)` shape now emits a one-shot
  `tracing::warn!` per algorithm per process flagging the planned
  removal in M5c.5.

### Migration

No source breakage. External plugins that implement `AlgoProcedure`
gain a default `execute_with_projection` that returns
`AlgoError::ProjectionInputUnsupported`; override it to gain V2 Cypher
/ Named support.

## 1.7.1 — 2026-05-24 — `register_index_handle` host API (M5 Batch 2 follow-up #4)

Additive host-side `PluginRegistry` API for live `IndexHandle` lookup by
index name. Enables the planner to route vector-KNN probes through a
custom `IndexKindProvider`'s handle instead of always dispatching to the
native storage path. The native path remains the fall-through when no
handle is registered (preserving the "no behavior change for built-ins"
invariant).

### Added

- **`pub struct IndexHandleEntry { kind, handle }`** in
  `crates/uni-plugin/src/registry.rs``Clone`able lookup payload.
- **`PluginRegistry::register_index_handle(name, kind, handle)`**  inserts a handle keyed by index name; replaces on duplicate.
- **`PluginRegistry::index_handle(name) -> Option<IndexHandleEntry>`**  cheap clone (inner `handle: Arc<dyn IndexHandle>`).
- **`PluginRegistry::deregister_index_handle(name)`** — removes and
  returns the prior entry.

### Migration

No source breakage. Existing callers that did not interact with index
handles are unaffected.

## 1.7.0 — 2026-05-24 — `OptimizerRuleProvider::physical_rule()` (M5h follow-up #2)

`OptimizerRuleProvider` grows an additive `physical_rule()` method that
returns `Option<Arc<dyn PhysicalOptimizerRule + Send + Sync>>`, enabling
plugin-registered physical-phase optimizer rules to be installed via
DataFusion's `SessionStateBuilder::with_physical_optimizer_rule`. The
default impl returns `None`, so existing logical-only providers compile
unchanged. The `rule()` method also gains a default that returns a
no-op rule, letting physical-only providers omit it. A new
`NoopOptimizerRule` public type backs that default.

### Added

- **`OptimizerRuleProvider::physical_rule()`** in
  `crates/uni-plugin/src/traits/operator.rs` — default `None`.
- **`OptimizerRuleProvider::rule()` gained a default impl** returning
  `Arc::new(NoopOptimizerRule)`.
- **`pub struct NoopOptimizerRule`** in
  `crates/uni-plugin/src/traits/operator.rs` — sentinel logical rule.

### Migration

No source breakage for existing providers — both methods have defaults.
Physical-phase providers should override `physical_rule()`; logical-only
providers continue overriding `rule()` as before.

## 1.6.0 — 2026-05-24 — AlgorithmContext gains opaque host handle (M5c.1)

`AlgorithmContext` now carries an optional `&dyn AlgorithmHost` callback
so plugin algorithms can downcast to the concrete host type
(`StorageManager` + `L0Manager` for the built-in bridge) without
`uni-plugin` taking upward dependencies on `uni-store` / `uni-algo`.
Direct struct-literal construction is forbidden by `#[non_exhaustive]`;
use [`AlgorithmContext::new`] / [`AlgorithmContext::with_host`].

### Added

- **`trait AlgorithmHost: Send + Sync`** in
  `crates/uni-plugin/src/traits/algorithm.rs` — opaque host callback
  with `fn as_any(&self) -> &dyn std::any::Any`.
- **`AlgorithmContext::new(config_json)`** and
  **`AlgorithmContext::with_host(host)`** builders.
- **`AlgorithmContext::host: Option<&'a dyn AlgorithmHost>`** field.

### Migration

`AlgorithmContext` is `#[non_exhaustive]` so direct struct-literal
construction was already forbidden outside the defining crate. Hosts
that previously used `AlgorithmContext { config_json: "…" }` inside
`uni-plugin` itself must switch to the builder.

## 1.5.0 — 2026-05-24 — Lance fork wiring (M5a follow-up #3)

`Storage::fork` grows a per-dataset `table` parameter and returns rich
metadata so callers can chain nested forks. The `LancePluginStorage`
adapter in `uni-plugin-builtin` now overrides both `supports_branching()`
and `fork()` to wire Lance-native branching through the plugin barrier.

### Added

- **`struct BranchMetadata { parent_version: u64, branch_name: String }`**
  in `crates/uni-plugin/src/traits/storage.rs` — surfaces the backend
  version pinned as the fork-point so caller-side nested-fork
  orchestration can chain without re-querying.

### Changed (breaking)

- **`Storage::fork`** signature was
  `async fn fork(&self, src_branch: &str, dst_branch: &str) -> Result<(), FnError>`;
  it is now
  `async fn fork(&self, table: &str, src_branch: &str, dst_branch: &str) -> Result<BranchMetadata, FnError>`.
  Granularity is per-dataset because real branching backends (Lance)
  track branches and versions independently per table. Multi-dataset
  orchestration stays the caller's responsibility (uni-store's
  `BranchedBackend` retains the multi-table coordination it already had).
  The default impl continues to return `FnError 0x10`, so non-branching
  backends are unaffected at runtime — only the signature changes.

### Why this matters

M5 Batch 1 (1.4.0) shipped `LancePluginStorage` but left `fork()` on the
trait's default no-op. M5 follow-up #3 closes that gap so plugin-backed
storage can participate in fork creation. The version field on the
returned metadata is the wire-feasibility bit for future nested-fork
support — callers don't have to round-trip back through the backend to
discover the parent version.

### Version policy

`crates/uni-plugin/Cargo.toml` overrides `version.workspace = true` with
`version = "1.5.0"`; the workspace stays on `1.3.0`. Other workspace
crates remain on the workspace version until they accumulate ABI
additions of their own.

## 1.4.0 — 2026-05-24 — phased context shape v1.1

`crates/uni-plugin/src/traits/hook.rs` grows three additions to surface
real query / commit metadata to phased hooks. All changes are additive
and back-compatible: existing constructors keep their signatures and the
new fields default to zero-valued placeholders.

### Added

- **`enum QueryType { Cypher, Locy, Execute }`** — classification of the
  query under observation. `#[derive(Default)]` (= `Cypher`). Mirrors
  `uni_db::api::hooks::QueryType` without taking a `uni-db` dep
  (circular).
- **`struct PluginCommitResult { mutations, version, wal_lsn, duration }`**
  — slim mirror of the host's commit metadata, surfaced to phased
  `after_commit` hooks. `Default::default()` is all zeros.
- **`ParseContext::query_type: QueryType`** — populated via
  `ParseContext::new(...).with_query_type(t)`; defaults to
  `QueryType::Cypher`.
- **`ParseContext::params: &'a [(SmolStr, ScalarValue)]`**  Arrow-shaped bound-parameter slice, populated via
  `ParseContext::new(...).with_params(&[...])`; defaults to `&[]`.
  Chosen over `HashMap<String, Value>` so `uni-plugin` doesn't grow a
  `uni-common` dep.
- **`CommitContext::commit_result: Option<&'a PluginCommitResult>`**  `None` in `before_commit`; `Some(_)` in `after_commit` once the host
  bridges the real result through.
- **Builders** on both contexts: `with_query_type`, `with_params`,
  `with_commit_result`.

### Why this matters

The M5e legacy-hook bridge (`LegacyHookAdapter` in `uni-db`) previously
synthesized zero-filled stubs because `ParseContext` carried no
query-type / params and `CommitContext` carried no result. With v1.1,
the bridge can route real values through, so legacy hooks observing the
phased path see the same metadata they'd see through the legacy
`Session::add_hook` HashMap.

### Version policy

This is the first `uni-plugin`-only minor bump since the workspace
adopted unified versioning. `crates/uni-plugin/Cargo.toml` overrides
`version.workspace = true` with an explicit `version = "1.4.0"`. Other
workspace crates stay on `1.3.0` until they accumulate their own ABI
additions. When the workspace later bumps to `1.4.0`, this override is
removed.

## 1.3.0 and earlier

See git history (`git log -- crates/uni-plugin`).