hive-c0re: fail on an unparseable permission file, write it atomically
tool_groups::read and capabilities::read returned an empty map when
their file existed but didn't parse. Every set_*/remove_agent is a
read-modify-write, and write() rewrote the file in place, so a crash or
ENOSPC mid-write left a truncated file, and the next write (e.g. the
manager-spawn seed of ruth's tool groups) replaced it with a map holding
only one agent. The scheduling and approval gates then denied every
other agent, recoverable only from meta git history.
- Both registries now read through agent_config::read_map: a missing
file is still the empty map, any other read failure or a parse
failure is an io::Error. set_groups / set_caps / remove_agent fail
without writing.
- Writes go through agent_config::write_map: temp file in the same
directory, fsync, rename, fsync the directory. hive-c0re had no
shared atomic-write helper (the existing tmp+rename sites are inline
and don't fsync).
- Callers of read / groups_for / has_cap now handle the error:
* dashboard GET /api/tool-groups, /api/capabilities,
/api/permissions/stale return 500 instead of an empty table;
* the SSE permission snapshots are skipped with a warn;
* render_flake returns Result, so sync_agents fails instead of
rendering every agent without its tool groups / capabilities;
* set_nspawn_flags propagates has_cap's error;
* the socket tool-group gates deny with the read error as message;
* seed_manager_tool_groups logs and does not seed.
- capabilities::write had no callers left once set_caps writes through
write_map, and is removed.
Closes #4719
This commit is contained in:
parent
4f3209d8c7
commit
e0b08fe362
9 changed files with 341 additions and 129 deletions
|
|
@ -598,15 +598,14 @@ async fn dispatch_orchestration(req: &Request, agent: &str, coord: &Arc<Coordina
|
|||
/// these verbs without any positional/hardcoded privilege. `action` is the
|
||||
/// verb phrase for the message.
|
||||
fn require_group(agent: &str, group: &str, action: &str) -> Option<Response> {
|
||||
if crate::tool_groups::groups_for(agent)
|
||||
.iter()
|
||||
.any(|g| g == group)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(Response::Err {
|
||||
match crate::tool_groups::groups_for(agent) {
|
||||
Ok(groups) if groups.iter().any(|g| g == group) => None,
|
||||
Ok(_) => Some(Response::Err {
|
||||
message: format!("agent `{agent}` cannot {action}: requires the `{group}` tool group"),
|
||||
})
|
||||
}),
|
||||
Err(e) => Some(Response::Err {
|
||||
message: format!("agent `{agent}` cannot {action}: {e}"),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -699,6 +698,7 @@ fn handle_cancel_loose_end(
|
|||
fn check_can_cancel_approval(canceller: &str) -> Result<(), String> {
|
||||
const APPROVALS_GROUP: &str = "approvals";
|
||||
if crate::tool_groups::groups_for(canceller)
|
||||
.map_err(|e| format!("cancel_loose_end: {e}"))?
|
||||
.iter()
|
||||
.any(|g| g == APPROVALS_GROUP)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue