feat(dashboard): batch POST /api/permissions for save-all perms (#1719)

This commit is contained in:
damocles 2026-06-17 18:08:19 +02:00
commit 8e24814efe
5 changed files with 163 additions and 0 deletions

View file

@ -357,6 +357,47 @@ pub async fn commit_capabilities(agent: &str, caps: &[String]) -> Result<()> {
Ok(())
}
/// Write both perm files for `agent` (whichever are `Some`) and commit
/// them in a SINGLE git commit under `META_LOCK` — the batch
/// `POST /api/permissions` path. A `None` field leaves that file
/// untouched. One commit + (caller does) one rebuild means changing an
/// agent's caps and tool-groups together no longer triggers two
/// rebuilds. Mirrors the staging discipline of `commit_tool_groups` /
/// `commit_capabilities`.
///
/// # Errors
///
/// Returns an error if writing either JSON file fails, a capability name
/// is invalid (`set_caps`), or a git stage/commit step fails.
pub async fn commit_perms(
agent: &str,
groups: Option<&[String]>,
caps: Option<&[String]>,
) -> Result<()> {
let _guard = META_LOCK.lock().await;
let dir = meta_dir();
let mut parts: Vec<&str> = Vec::new();
if let Some(groups) = groups {
crate::tool_groups::set_groups(agent, groups)?;
if crate::tool_groups::tool_groups_path().exists() {
git(&dir, &["add", "tool-groups.json"]).await?;
}
parts.push("tool-groups");
}
if let Some(caps) = caps {
crate::capabilities::set_caps(agent, caps)
.map_err(|e| anyhow::anyhow!("set capabilities for {agent}: {e}"))?;
if crate::capabilities::capabilities_path().exists() {
git(&dir, &["add", "capabilities.json"]).await?;
}
parts.push("capabilities");
}
if has_staged_changes(&dir).await? {
git_commit(&dir, &format!("set {} for {agent}", parts.join(" + "))).await?;
}
Ok(())
}
/// Write the topology file and commit it atomically under `META_LOCK`.
/// Returns `Err(String)` on validation failure (unknown agent, cycle,
/// etc.) — same shape as `topology::set_parent` — so callers can