feat(spawner): post-spawn vault provision hook (HIVE step 3) #87
4 changed files with 77 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -330,6 +330,7 @@ dependencies = [
|
||||||
"colibri-glasspane",
|
"colibri-glasspane",
|
||||||
"colibri-runtime",
|
"colibri-runtime",
|
||||||
"colibri-store",
|
"colibri-store",
|
||||||
|
"colibri-vault",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ colibri-deepseek = { path = "../colibri-deepseek" }
|
||||||
colibri-glasspane = { path = "../colibri-glasspane" }
|
colibri-glasspane = { path = "../colibri-glasspane" }
|
||||||
colibri-runtime = { path = "../colibri-runtime" }
|
colibri-runtime = { path = "../colibri-runtime" }
|
||||||
colibri-store = { path = "../colibri-store" }
|
colibri-store = { path = "../colibri-store" }
|
||||||
|
colibri-vault = { path = "../colibri-vault" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-util = { version = "0.7", features = ["codec"] }
|
tokio-util = { version = "0.7", features = ["codec"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,56 @@ async fn memory_handoff(state: &SharedState) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Post-spawn hook: if a jailed agent has a matching tenant record,
|
||||||
|
/// call colibri-vault to provision the jail's .env before agent startup.
|
||||||
|
pub(crate) async fn provision_tenant_env(
|
||||||
|
state: &SharedState,
|
||||||
|
jail_name: &str,
|
||||||
|
jail_root_path: &str,
|
||||||
|
) {
|
||||||
|
// Check if this jail is a registered tenant (scope the lock)
|
||||||
|
let tenant = {
|
||||||
|
let store = state.store.lock().unwrap();
|
||||||
|
match store.get_tenant(jail_name) {
|
||||||
|
Ok(Some(t)) => Some(t),
|
||||||
|
Ok(None) => {
|
||||||
|
return; // not a tenant — nothing to provision
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(jail = jail_name, error = %e, "tenant lookup failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tenant = match tenant {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
jail = jail_name,
|
||||||
|
collection = tenant.collection_id,
|
||||||
|
"provisioning tenant env from vault"
|
||||||
|
);
|
||||||
|
|
||||||
|
match colibri_vault::provision(&tenant.collection_id, jail_root_path).await {
|
||||||
|
Ok(result) => {
|
||||||
|
info!(
|
||||||
|
jail = jail_name,
|
||||||
|
items = result.items_written,
|
||||||
|
bytes = result.bytes_written,
|
||||||
|
"vault provision complete"
|
||||||
|
);
|
||||||
|
let store = state.store.lock().unwrap();
|
||||||
|
let _ = store.set_tenant_status(jail_name, "active");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(jail = jail_name, error = %e, "vault provision failed — agent will start without .env");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn poll_tasks(state: &SharedState) {
|
pub async fn poll_tasks(state: &SharedState) {
|
||||||
debug!("task polling tick");
|
debug!("task polling tick");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use crate::spawner::{AgentSpawnConfig, JailConfig, Provider, Spawner};
|
use crate::spawner::{AgentSpawnConfig, JailConfig, Provider, Spawner};
|
||||||
|
use crate::daemon::provision_tenant_env;
|
||||||
use crate::{ColibriCommand, ColibriResponse, SharedState};
|
use crate::{ColibriCommand, ColibriResponse, SharedState};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -443,6 +444,14 @@ async fn cmd_spawn_agent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract jail info before spawn consumes agent_config
|
||||||
|
let jail_for_provision = agent_config.jail.as_ref().and_then(|j| {
|
||||||
|
j.root_path.as_ref().map(|root| {
|
||||||
|
let name = root.split('/').nth(4).unwrap_or("unknown").to_string();
|
||||||
|
(name, root.clone())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
// Merge extra env into agent config
|
// Merge extra env into agent config
|
||||||
let mut agent_config = agent_config;
|
let mut agent_config = agent_config;
|
||||||
agent_config.env.extend(extra_env);
|
agent_config.env.extend(extra_env);
|
||||||
|
|
@ -472,6 +481,22 @@ async fn cmd_spawn_agent(
|
||||||
);
|
);
|
||||||
state.agents.insert(id.clone(), handle);
|
state.agents.insert(id.clone(), handle);
|
||||||
|
|
||||||
|
// Post-spawn hook: if agent was spawned in a jail, provision tenant env
|
||||||
|
if let Some((jail_name, root)) = &jail_for_provision {
|
||||||
|
if !jail_name.is_empty() && jail_name != "unknown" {
|
||||||
|
let jail_name = jail_name.clone();
|
||||||
|
let root = root.clone();
|
||||||
|
let state_clone = state.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
provision_tenant_env(
|
||||||
|
&state_clone,
|
||||||
|
&jail_name,
|
||||||
|
&root,
|
||||||
|
).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(stdout) = stdout {
|
if let Some(stdout) = stdout {
|
||||||
let reader_state = state.clone();
|
let reader_state = state.clone();
|
||||||
let reader_agent_id = id.clone();
|
let reader_agent_id = id.clone();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue