agent permissions: drop hive-c0re's swarm-agent-permissions.json fallback, fail closed before the first read
Removes the HiveCopy hand-off: hive-c0re no longer writes each agent's entry from swarm-agent-permissions.json into its runtime dir as swarm-permissions.json, and the harness and subagent daemon no longer read it. hive-c0re keeps the file as its own last read of the swarm for the manage_root_agent mounts. Also removes permissions_import::pending(), its only caller being the hand-off. No nix provisioned the copy, so no nix option is removed and no tombstone is needed. Rule after this change: until an agent's first successful read of its key it holds no permissions (no execution, so no bash), and a subagent it starts holds the same. After a read a set key wins, [] holds none and an unset key is the default. A failed read keeps the last value, so an unreachable queue at start keeps the agent closed until a read succeeds. The harness logs once at warn that it starts closed. Fixes #5315
This commit is contained in:
parent
6e7b5f84e9
commit
2f00f9041f
11 changed files with 121 additions and 283 deletions
|
|
@ -20,7 +20,6 @@
|
|||
//! [`Read::Failed`]: the caller checks against `HIVE_AVAILABLE_MODELS` and
|
||||
//! applies no cap.
|
||||
|
||||
use std::path::Path;
|
||||
#[cfg(test)]
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
|
@ -28,7 +27,7 @@ use std::time::Duration;
|
|||
|
||||
use hive_types::Permission;
|
||||
use swarm_queue_client::agent_inference::{self, read_own};
|
||||
use swarm_queue_client::agent_permissions::{self, HIVE_COPY_PATH, HiveCopy};
|
||||
use swarm_queue_client::agent_permissions::{self, BEFORE_FIRST_READ, held};
|
||||
use swarm_wire::agent_inference::AgentInference;
|
||||
|
||||
use crate::swarm_term::{Target, connect};
|
||||
|
|
@ -38,15 +37,15 @@ use crate::swarm_term::{Target, connect};
|
|||
static LAST_PERMISSIONS: std::sync::RwLock<Option<Vec<Permission>>> = std::sync::RwLock::new(None);
|
||||
|
||||
/// The permissions this agent holds now, for the subagent being spawned: the
|
||||
/// last set read, or what hive-c0re stored for the agent before any read
|
||||
/// succeeds ([`HiveCopy::before_read`]).
|
||||
/// last set read, or nothing before any read succeeds
|
||||
/// ([`BEFORE_FIRST_READ`]).
|
||||
#[must_use]
|
||||
pub fn permissions_now() -> Vec<Permission> {
|
||||
let last = LAST_PERMISSIONS
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
last.unwrap_or_else(|| HiveCopy::read_at(Path::new(HIVE_COPY_PATH)).before_read())
|
||||
last.unwrap_or_else(|| BEFORE_FIRST_READ.to_vec())
|
||||
}
|
||||
|
||||
/// How long after a failed connect the task tries again.
|
||||
|
|
@ -150,8 +149,8 @@ impl Reader {
|
|||
}
|
||||
|
||||
/// Read the permissions the swarm grants this agent and keep them for
|
||||
/// [`permissions_now`], judged by [`HiveCopy::after_read`]. A read that
|
||||
/// fails keeps the last value.
|
||||
/// [`permissions_now`], with an unset key the default. A read that fails keeps the
|
||||
/// last value.
|
||||
pub async fn refresh_permissions(&self) {
|
||||
let read = match &self.source {
|
||||
Source::Queue { agent, client } => read_permissions(agent, client).await,
|
||||
|
|
@ -173,9 +172,7 @@ async fn read_permissions(
|
|||
let client = client.get()?;
|
||||
swarm_queue_client::ensure_connected(client).ok()?;
|
||||
match tokio::time::timeout(READ_TIMEOUT, agent_permissions::read(client, agent)).await {
|
||||
Ok(Ok(agent_permissions::Read::Current(record))) => {
|
||||
Some(HiveCopy::read_at(Path::new(HIVE_COPY_PATH)).after_read(&record))
|
||||
}
|
||||
Ok(Ok(agent_permissions::Read::Current(record))) => Some(held(&record)),
|
||||
Ok(Ok(agent_permissions::Read::NoBucket)) => None,
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!(error = %e, "subagent permissions: reading this agent's key failed");
|
||||
|
|
@ -238,4 +235,16 @@ mod tests {
|
|||
let client = OnceLock::new();
|
||||
assert!(matches!(read_queue("iris", &client).await, Read::Failed(_)));
|
||||
}
|
||||
|
||||
/// No test sets `LAST_PERMISSIONS` (a fixed reader never reads the
|
||||
/// permissions), so this is the state before the first read.
|
||||
#[test]
|
||||
fn before_the_first_read_a_subagent_gets_no_execution() {
|
||||
let held = permissions_now();
|
||||
assert!(held.is_empty(), "held {held:?} before any read");
|
||||
assert!(
|
||||
Permission::DEFAULT.contains(&Permission::Execution),
|
||||
"control: the default does hold it"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue