Skip to main content

PluginHost

Struct PluginHost 

pub struct PluginHost { /* private fields */ }
Expand description

Manages the lifecycle of WASM plugins.

Implementations§

Source§

impl PluginHost

Source

pub fn new(workspace_dir: &Path) -> Result<Self, PluginError>

Create a new plugin host rooted at workspace_dir, scanning its plugins/ subdirectory.

Source

pub fn with_security( workspace_dir: &Path, signature_mode: SignatureMode, trusted_publisher_keys: Vec<String>, ) -> Result<Self, PluginError>

Create a host rooted at workspace_dir (scanning workspace_dir/plugins) with signature verification settings.

Source

pub fn from_plugins_dir(plugins_dir: &Path) -> Result<Self, PluginError>

Create a host that scans plugins_dir directly (no plugins/ suffix is appended). Use this when the caller already holds the fully resolved plugin directory, e.g. PluginsConfig::resolved_plugins_dir().

Source

pub fn from_plugins_dir_with_security( plugins_dir: &Path, signature_mode: SignatureMode, trusted_publisher_keys: Vec<String>, ) -> Result<Self, PluginError>

Self::from_plugins_dir with signature verification settings.

Source

pub fn parse_signature_mode(mode: &str) -> Option<SignatureMode>

Source

pub fn resolve_signature_mode(mode: &str) -> SignatureMode

Source

pub fn list_plugins(&self) -> Vec<PluginInfo>

List all discovered plugins.

Sorted by package name so the listing is stable across runs: the loaded set is a hash map, and an operator diffing two plugin list outputs, or a script reading the verified rows, needs the order to mean nothing.

Source

pub fn get_plugin(&self, name: &str) -> Option<PluginInfo>

Get info about a specific plugin.

Source

pub fn manifest(&self, name: &str) -> Option<&PluginManifest>

Return the admitted manifest that owns a plugin’s runtime contract.

Source

pub fn manifest_toml(&self, name: &str) -> Option<&str>

The manifest text an installed plugin’s Self::manifest was parsed from and its signature was checked against: the bytes this host loaded, not the file as it may read now.

Source

pub fn admitted_component(&self, name: &str) -> Option<&AdmittedComponent>

The exact component bytes admitted for an installed plugin: the bytes the daemon compiles, so a load check against them checks what will run. None for an unknown plugin and for a skill-only plugin that ships no WASM.

Source

pub fn install(&mut self, source: &str) -> Result<String, PluginError>

Install a plugin from a directory path. Returns the installed plugin’s manifest name so callers can key follow-up work (config seeding, messaging) off the canonical name rather than the source path.

This is Self::admit_source followed by Self::install_admitted. A caller that wants to load-check the component in between (the CLI’s install-time verification) calls the two halves itself; either way, the bytes that were admitted are the bytes that get installed.

Source

pub fn admit_source(&self, source: &str) -> Result<AdmittedSource, PluginError>

Admit a source without installing it.

Parses and signature-checks the manifest, validates its shape and config, refuses a name the host has already loaded, and reads the component once through the same confined, size-bounded, digest-checked admission the host applies to installed plugins. Everything Self::install_admitted persists comes from the returned AdmittedSource and never from source again, so whatever a caller verifies between the two calls is, byte for byte, what gets installed.

The duplicate-name check runs before the component is read, so a package the host would refuse anyway never reaches the loader.

Source

pub fn admit_update( &self, name: &str, source: &str, ) -> Result<AdmittedSource, PluginError>

Admit a source as the replacement for the installed package name.

This is Self::admit_source with the identity decided the other way round: name must be loaded, which is checked before the source is opened, and the source’s manifest must name it, which is checked before the signature and before the component is read. Everything else, from signature policy to the one confined read of the component, is the same admission a new package gets.

Source

pub fn install_admitted( &mut self, admitted: AdmittedSource, ) -> Result<String, PluginError>

Install a source admitted by Self::admit_source.

Persists the manifest bytes that were parsed and signature-checked and the component bytes that were admitted, so the file a verifier checked is the file the daemon will load. The skills/ subtree of a skill-capable package is copied from the source directory as before; it is data, not executable code.

Source

pub fn remove(&mut self, name: &str) -> Result<(), PluginError>

Remove a plugin by name.

For a loaded plugin, the directory at its name is deleted. For a name the host did not load, remove is the recovery path for an interrupted install: it claims and validates the exact directory generation and cleans only staging whose protocol lease proves abandonment. Ambiguous legacy staging is retained. The final directory must be empty, or hold a manifest.toml naming name and nothing an install never writes, with admission rejecting its own contents rather than its signature. That includes a package this host cannot accept as written, such as one whose component exceeds the admission size limit. Anything else at the name is left untouched and reported as PluginError::UnadmittedPackage with the reason: a symlink or file, a directory holding files but no manifest, one holding anything an install never writes or whose manifest names another package, one that cannot be identified, inspected, or listed, a loaded package’s directory, a package admission accepts, and one this host rejects for its signature policy. A refusal restores the claimed package without replacing a concurrent occupant that holds files; if restoration cannot complete, RecoveryRetained names the retained transaction and a later remove retries it.

Source

pub fn remove_with_report( &mut self, name: &str, ) -> Result<Vec<PathBuf>, PluginError>

Removal result includes ambiguous staging paths that were deliberately retained. Callers displaying a recovery result must surface these paths.

Source

pub fn tool_plugins(&self) -> Vec<&PluginManifest>

Get tool-capable plugins.

Source

pub fn tool_plugin_details(&self) -> Vec<(&PluginManifest, &AdmittedComponent)>

Get tool-capable plugins with their admitted executable bytes.

Source

pub fn channel_plugins(&self) -> Vec<&PluginManifest>

Get channel-capable plugins.

Source

pub fn channel_plugin_details( &self, ) -> Vec<(&PluginManifest, &AdmittedComponent)>

Source

pub fn skill_plugins(&self) -> Vec<&PluginManifest>

Get skill-capable plugins.

Source

pub fn skill_plugin_details(&self) -> Vec<(&PluginManifest, PathBuf)>

Source

pub fn plugins_dir(&self) -> &Path

Returns the plugins directory path.

Source§

impl PluginHost

Updates: replacing an installed package with an admitted source, and recovering what an interrupted replacement left.

Every step runs under the package lock that serializes install, remove and recovery across hosts. The replacement is built in a leased stage, as install builds a package, and the installed generation is claimed into a leased transaction of its own before the replacement is published in its place. Both moves are renames that never overwrite, and a claimed generation is deleted only through its own handle. Discovery ignores the hidden transaction directories, so neither a half-built replacement nor a claimed generation is ever loaded, and a lease that outlived its process is how recovery tells an interrupted update from one still running.

Source

pub fn update_admitted( &mut self, admitted: AdmittedSource, ) -> Result<ReplacedPackage, PluginError>

Replace the installed package that admitted names.

admitted comes from Self::admit_update. The replacement is staged as Self::install_admitted stages a new package, and the staged skill bundle of a skill-capable package is validated as discovery will validate it: staging copies no symbolic links, so a bundle admission accepted can stage incomplete. Only then is the installed generation claimed. If its manifest text or admitted component differs from the generation this host loaded, it is published back and the update refused, because the caller compared authority against the generation it loaded. The replacement is then published in the directory the installed package was discovered in, and if that fails the claimed generation is published back. So on every error except PluginError::ReplacementInterrupted the installed package is exactly as it was.

Configuration and durable state are not touched: they belong to the instance identity, which does not include the version.

§Preconditions

This method does not compare authority. The runtime grants a plugin what its manifest requests, so before calling it the caller must compare the authority admitted requests (its permissions, capabilities, provides ids and publisher) with the generation this host loaded, and have the operator accept any increase. What this method guarantees is that the generation it replaces is the one that comparison was made against.

§Errors
  • PluginError::NotFound when no package of that name is loaded.
  • PluginError::NamespaceChanged when the installed package is not the generation this host loaded, or the plugins directory changed. The installed package is put back first.
  • An I/O error when the package lock is still held after 60 seconds, or when staging, claiming or publishing fails, and PluginError::InvalidManifest when the staged skill bundle is not what discovery would load. The installed package is untouched or put back.
  • PluginError::ReplacementInterrupted when the claimed generation could not be put back either. It names where that generation is preserved, and this host no longer holds the package as loaded.
Source

pub fn recover_interrupted_update( &mut self, name: &str, ) -> Result<UpdateRecovery, PluginError>

Put back a package an interrupted update left claimed, or delete the generations finished updates left behind.

Runs under the package lock and judges only replacing transactions whose lease it can take: a lease still held belongs to a process that has not finished, and its claimed generation is never touched. Whether the package is installed is judged from the plugins directory as it is under the lock, not from this host’s earlier view, which can predate the update that left the claims. Only name is judged again, and every other package keeps this host’s earlier view, so a batch that recovers each of its names does not admit every installed package once per name. When it is installed, every claimed generation of it is out of date, so each is marked for deletion and deleted through its handle; one that cannot be is reported and left. When it is not, the one generation an interrupted update claimed is published back to <plugins_dir>/<name>, so the restored package is admitted only if the configured policy accepts it, like any other. An installed package of the name takes precedence over any claimed generation. Several claimed generations, or anything already at the name, are reported and left as found; a generation marked for deletion is never put back; and a claim that holds neither is finished. Stages that interrupted updates of name left are swept as remove sweeps them. A batch that did not name the package calls Self::sweep_interrupted_update instead, which never puts one back.

Source

pub fn sweep_interrupted_update( &mut self, name: &str, ) -> Result<UpdateRecovery, PluginError>

Self::recover_interrupted_update for a batch that did not name the package. Generations finished updates left are deleted the same way, but a package an interrupted update displaced is left where it is and reported as UpdateRecovery::Displaced, so only recovering it by name puts it back. That is judged under the lock, so a package displaced after the batch chose its names is left in place too.

Source

pub fn displaced_packages(&self) -> Result<Vec<String>, PluginError>

Packages an interrupted update left claimed: sorted names that are not loaded and have an abandoned claim that holds a package. Self::recover_interrupted_update puts such a package back, or, with several claims or an occupied name, reports why it cannot. Runs under the package lock, so it never holds a claim’s lease while a recovery judges it.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more