feat: bind Plugin launch selections and authenticated Runtime providers

This commit is contained in:
2026-10-30 05:28:51 +09:00
parent 0cd4811739
commit 479bf0872a
17 changed files with 1298 additions and 38 deletions
+36 -9
View File
@@ -232,8 +232,9 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
_ => fs_paths.runtime_dir.clone(),
};
let mut factory = ProfileRuntimeWorkerFactory::new(fs_paths.worker_dir.join("worker-root"))
.with_runtime_store_dir(runtime_store_dir);
.with_runtime_store_dir(runtime_store_dir.clone());
let runtime_auth = read_runtime_auth_file(&runtime_auth_path(config))?;
let mut plugin_clients = std::collections::HashMap::new();
if let Some(identity) = runtime_auth.identity.clone() {
factory = factory.with_remote_worker_mutation_identity(identity);
}
@@ -243,16 +244,42 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
.iter()
.filter(|issuer| issuer.state == WorkspaceIssuerTrustState::Active)
{
factory = factory.with_workspace_request_client(
RuntimeWorkspaceRequestClient::new(
issuer.workspace_id.clone(),
issuer.backend_url.clone(),
identity.identity_id.clone(),
)
.with_runtime_request_source(identity, issuer.backend_url.clone()),
);
let client = RuntimeWorkspaceRequestClient::new(
issuer.workspace_id.clone(),
issuer.backend_url.clone(),
identity.identity_id.clone(),
)
.with_runtime_request_source(identity, issuer.backend_url.clone());
plugin_clients.insert(issuer.workspace_id.clone(), client.clone());
factory = factory.with_workspace_request_client(client);
}
}
if !plugin_clients.is_empty() {
let auth_path = runtime_auth_path(config);
let current: Arc<worker_runtime::plugin::remote::CurrentPluginServer> =
Arc::new(move |workspace| {
read_runtime_auth_file(&auth_path)
.ok()
.and_then(|auth| {
auth.workspace_issuers
.into_iter()
.find(|issuer| issuer.workspace_id == workspace)
})
.ok_or(yoi_plugin_runtime::delivery::DeliveryError::Denied)
});
let providers = Arc::new(
worker_runtime::plugin::remote::RemotePluginRuntimeProviders::new(
plugin_clients,
current,
),
);
let plugins = worker_runtime::plugin::PinnedPluginToolRuntime::new(
providers,
&runtime_store_dir.join("plugin-cache"),
)
.map_err(ProcessError::WorkerAdapter)?;
factory = factory.with_plugin_runtime(Arc::new(plugins));
}
let mut backend_resource_client: Option<
Arc<dyn worker_runtime::resource::BackendResourceClient>,
> = None;
+205
View File
@@ -85,3 +85,208 @@ pub async fn materialize(
.await
.map_err(|error| error.to_string())
}
pub struct PluginRuntimeServices {
pub artifacts: std::sync::Arc<dyn yoi_plugin_runtime::delivery::ArtifactSource>,
pub owner: std::sync::Arc<dyn yoi_plugin_runtime::owner::PluginHostOwner>,
}
/// Supplies authenticated process-owned adapters, not a Plugin resolver. Handles
/// are bound to the exact requested Worker; a connection failure has no fallback.
#[async_trait]
pub trait PluginRuntimeProviders: Send + Sync {
async fn connect(
&self,
workspace: &str,
worker: &WorkerRef,
) -> Result<PluginRuntimeServices, String>;
}
pub struct PinnedPluginToolRuntime {
providers: std::sync::Arc<dyn PluginRuntimeProviders>,
cache: yoi_plugin_runtime::delivery::ArtifactCache,
limits: yoi_plugin_runtime::Limits,
}
impl PinnedPluginToolRuntime {
pub fn new(
providers: std::sync::Arc<dyn PluginRuntimeProviders>,
private_cache_root: &std::path::Path,
) -> Result<Self, String> {
Ok(Self {
providers,
cache: yoi_plugin_runtime::delivery::ArtifactCache::new(
private_cache_root,
yoi_plugin_runtime::delivery::CacheLimits::default(),
)
.map_err(|_| "Plugin cache initialization failed")?,
limits: yoi_plugin_runtime::Limits::default(),
})
}
}
#[async_trait]
impl PluginToolRuntime for PinnedPluginToolRuntime {
async fn prepare(
&self,
worker: &WorkerRef,
snapshot: &runtime_api::PluginExecutionSnapshot,
session_id: &str,
) -> Result<worker::plugin::PluginToolsFeature, String> {
snapshot
.plan
.validate_canonical()
.map_err(|_| "invalid Plugin plan")?;
if snapshot
.plan
.fingerprint()
.map_err(|_| "invalid Plugin plan")?
!= snapshot.fingerprint
|| snapshot.plan.execution().worker_id != worker.worker_id.to_string()
{
return Err("Plugin launch identity mismatch".into());
}
let services = self
.providers
.connect(snapshot.plan.workspace_id(), worker)
.await?;
for pin in snapshot.plan.plugins() {
services
.owner
.prepare(
snapshot.plan_id.clone(),
pin.installation_id().into(),
pin.projection().tools.clone(),
)
.await
.map_err(|_| "Plugin startup admission refused or unavailable")?;
}
let tools = materialize(
worker,
snapshot,
session_id,
services.artifacts.as_ref(),
services.owner.clone(),
&self.cache,
self.limits.clone(),
)
.await?;
// Materialization and compilation can wait on I/O. Recheck at publication
// preparation, including pure Tools and cache hits, without making a call.
for pin in snapshot.plan.plugins() {
services
.owner
.prepare(
snapshot.plan_id.clone(),
pin.installation_id().into(),
pin.projection().tools.clone(),
)
.await
.map_err(|_| "Plugin startup publication refused or unavailable")?;
}
Ok(tools)
}
}
#[cfg(feature = "http-server")]
pub mod remote {
use super::*;
use crate::{
workspace_issuer::WorkspaceIssuerTrustRecord,
workspace_request::{
RuntimeWorkspaceRequestClient,
plugin_delivery::{CurrentArtifactServer, RemotePluginArtifactSource},
},
};
use std::{collections::HashMap, sync::Arc, time::Duration};
pub type CurrentPluginServer = dyn Fn(&str) -> Result<WorkspaceIssuerTrustRecord, yoi_plugin_runtime::delivery::DeliveryError>
+ Send
+ Sync;
pub struct RemotePluginRuntimeProviders {
clients: HashMap<String, RuntimeWorkspaceRequestClient>,
current: Arc<CurrentPluginServer>,
}
impl RemotePluginRuntimeProviders {
pub fn new(
clients: HashMap<String, RuntimeWorkspaceRequestClient>,
current: Arc<CurrentPluginServer>,
) -> Self {
Self { clients, current }
}
}
struct RegisteredOwner {
source: Arc<RemotePluginArtifactSource>,
owner: Arc<dyn yoi_plugin_runtime::owner::PluginHostOwner>,
}
#[async_trait]
impl yoi_plugin_runtime::owner::PluginHostOwner for RegisteredOwner {
async fn call(
&self,
request: server_api::PluginHostRequest,
) -> Result<server_api::PluginHostResponse, server_api::PluginHostError> {
let denied = || server_api::PluginHostError {
code: server_api::PluginHostErrorCode::Denied,
effect: server_api::PluginEffectObservation::NotDispatched,
diagnostic_id: String::new(),
};
self.source.registration().map_err(|_| denied())?;
let dispatched = matches!(request, server_api::PluginHostRequest::Send { .. });
let response = self.owner.call(request).await?;
self.source
.registration()
.map_err(|_| server_api::PluginHostError {
code: server_api::PluginHostErrorCode::Denied,
effect: if dispatched {
server_api::PluginEffectObservation::Indeterminate
} else {
server_api::PluginEffectObservation::NotDispatched
},
diagnostic_id: String::new(),
})?;
Ok(response)
}
}
#[async_trait]
impl PluginRuntimeProviders for RemotePluginRuntimeProviders {
async fn connect(
&self,
workspace: &str,
worker: &WorkerRef,
) -> Result<PluginRuntimeServices, String> {
let client = self
.clients
.get(workspace)
.ok_or("Plugin Server is not registered")?
.clone();
let current = self.current.clone();
let workspace = workspace.to_owned();
let registered: Arc<CurrentArtifactServer> = Arc::new(move || current(&workspace));
let source = Arc::new(
RemotePluginArtifactSource::new(
client.clone(),
worker.worker_id.to_string(),
registered,
)
.map_err(|_| "Plugin Server trust unavailable")?,
);
let generated = client
.server_api_client(
crate::auth::WORKSPACE_REQUEST_PERMISSION,
Some(worker.worker_id.to_string()),
reqwest::header::HeaderMap::new(),
Duration::from_secs(30),
8 * 1024 * 1024,
)
.map_err(|_| "Plugin Host client unavailable")?;
let owner = Arc::new(RegisteredOwner {
source: source.clone(),
owner: Arc::new(yoi_plugin_runtime::owner::RemotePluginHostOwner::new(
Arc::new(generated),
client.workspace_id().into(),
)),
});
Ok(PluginRuntimeServices {
artifacts: source,
owner,
})
}
}
}
@@ -174,6 +174,23 @@ impl RuntimeWorkspaceRequestClient {
&& self.base_url.trim_end_matches('/') == base_url.trim_end_matches('/')
}
/// Expose the existing Runtime signer only as a request-bound authorizer for
/// the in-process Plugin Host adapter. This does not authenticate a request;
/// the Server still verifies its method/path/body proof on every operation.
pub fn plugin_host_authorizer(
&self,
worker_id: String,
) -> std::sync::Arc<dyn server_api::client_support::RequestAuthorizer> {
std::sync::Arc::new(RuntimeWorkspaceRequestAuthorizer {
workspace_id: self.workspace_id.clone(),
runtime_id: self.runtime_id.clone(),
request_source: self.request_source.clone(),
permission: crate::auth::WORKSPACE_REQUEST_PERMISSION.into(),
worker_id: Some(worker_id),
extra_headers: HeaderMap::new(),
})
}
pub(crate) fn server_api_client(
&self,
permission: impl Into<String>,
@@ -42,7 +42,7 @@ impl RemotePluginArtifactSource {
Ok(source)
}
fn registration(&self) -> Result<(), DeliveryError> {
pub(crate) fn registration(&self) -> Result<(), DeliveryError> {
let registration = (self.current_server)()?;
if registration.state != WorkspaceIssuerTrustState::Active
|| registration.workspace_id != self.client.workspace_id