Plugin System#
oxo-flow supports a compile-time + config-based plugin architecture. Plugins
are Rust crates implementing standard traits, registered via TOML configuration
files. Plugin manifests are integrity-checked using keyed SHA-256 signatures
(SHA256(key ‖ message)). Note: this is not a true HMAC (RFC 2104) and does
not provide cryptographic authentication — it protects against accidental
corruption only.
Quick Start#
Inert as of v0.23.2: the
[plugins]section, registry API, and manifest discovery are parsed but nothing is executed from them yet — see the warning at the end of this page. The API below is the compile-time contract for engine-side wiring.
1. Implement a plugin trait#
use oxo_flow_core::plugin::RulePlugin;
use oxo_flow_core::rule::Rule;
use oxo_flow_core::error::Result;
use std::collections::HashMap;
struct MyPlugin;
impl RulePlugin for MyPlugin {
fn rule_type(&self) -> &str { "my-custom-type" }
fn build_command(&self, rule: &Rule, values: &HashMap<String, String>) -> Result<String> {
Ok(format!("custom_tool --input {}", rule.input[0]))
}
fn validate(&self, rule: &Rule) -> Result<()> { Ok(()) }
}
2. Register with the registry#
use oxo_flow_core::plugin::PluginRegistry;
let mut registry = PluginRegistry::default();
registry.register_rule(Box::new(MyPlugin));
registry.trusted_keys.insert("key-001".into(), "your-secret-key".into());
3. Declare in your workflow#
[plugins]
rules = ["my-custom-type"]
executor = "slurm-custom"
reports = ["native-pdf"]
trusted_keys_file = ".oxo-flow/trusted_keys.toml"
Available Traits#
| Trait | Purpose | Key Method |
|---|---|---|
RulePlugin |
Custom rule types | build_command() |
ExecutorPlugin |
Custom executors | submit() |
ReportPlugin |
Custom report renderers | render() |
Plugin Discovery#
Plugins are discovered from .plugin.toml files in:
~/.oxo-flow/plugins/— user-level (shared across projects)<project>/.oxo-flow/plugins/— project-level
# my-plugin.plugin.toml
name = "my-custom-type"
version = "1.0.0"
plugin_type = "rule"
description = "Custom rule for specialized analysis"
author = "Your Name"
command_template = "custom_tool {input} > {output}"
environment = "bioconda::custom-tool=1.0"
[signature]
key_id = "key-001"
value = "a1b2c3d4..."
Signature Verification#
Each plugin manifest can include a [signature] section with a keyed
SHA-256 digest. During discovery, the registry verifies signatures against
trusted keys when any are configured; plugins with invalid signatures are
skipped, while unsigned plugins and plugins with signatures but no
configured trusted keys are loaded with a warning:
registry.trusted_keys.insert("key-001".into(), "shared-secret-key".into());
registry.discover(Some(project_dir))?; // invalid signatures are skipped
The signature covers all manifest fields except [signature] itself —
name, version, plugin_type, description, author, command_template, and
environment — encoded as a length-prefixed payload (<len>:<value> per
field) so a tampered command_template (the field a rule plugin would
execute) can never keep a valid signature. Older manifests signed with the
previous name:version:type:description payload no longer verify.
API Reference#
PluginRegistry#
impl PluginRegistry {
pub fn register_rule(&mut self, plugin: Box<dyn RulePlugin>);
pub fn register_executor(&mut self, plugin: Box<dyn ExecutorPlugin>);
pub fn register_report(&mut self, plugin: Box<dyn ReportPlugin>);
pub fn discover(&mut self, project_dir: Option<&Path>) -> Result<usize>;
pub fn find_rule(&self, rule_type: &str) -> Option<&dyn RulePlugin>;
pub fn find_executor(&self, backend: &str) -> Option<&dyn ExecutorPlugin>;
pub fn find_report(&self, renderer: &str) -> Option<&dyn ReportPlugin>;
}
// Public fields (constructed via PluginRegistry::default()):
pub struct PluginRegistry {
pub rule_plugins: HashMap<String, Box<dyn RulePlugin>>,
pub executor_plugins: HashMap<String, Box<dyn ExecutorPlugin>>,
pub report_plugins: HashMap<String, Box<dyn ReportPlugin>>,
pub manifests: Vec<PluginManifest>,
pub trusted_keys: HashMap<String, String>,
}
PluginsConfig (TOML [plugins] section)#
pub struct PluginsConfig {
pub rules: Vec<String>, // Rule plugin types to enable
pub executor: Option<String>, // Executor plugin to use
pub reports: Vec<String>, // Report plugins to enable
pub trusted_keys_file: Option<String>, // Path to keys file
}
Subprocess Output Contract#
oxo-flow avoids shared-library loading (no unsafe code). A PluginOutput
struct defines the JSON shape a plugin executable may emit on stdout, so a
manifest can eventually hand command construction to an external process —
plugins written in any language can produce it:
{
"success": true,
"command": "custom_tool --input raw/sample.fq --threads 8 > results/output.txt",
"errors": [],
"logs": ["Processing sample..."],
"exit_code": 0
}
success and command are the fields the engine would act on; errors,
logs, and exit_code carry diagnostics. Note that as of v0.23.2 the
whole plugin surface is defined but inert: a workflow [plugins] section
is parsed and logs a one-time warning ("parsed but not executed by this
version"), and there is no engine-side runner that invokes plugin
executables or consumes command_template yet — the registry API is
compile-time only.