Watch
0
0
Fork
You've already forked hyperhive
0

hive-c0re: read each agent's swarm caps from agent-limits

Every 30 seconds `workers::swarm_limits` reads the `agent-limits` key of
each agent this hive has and stores the result in
swarm-resource-limits.json, where the drop-in, the heap ceiling and the
dashboard read it. A cap therefore follows its agent to whichever hive
runs it.

Each value goes through the hive's own validate_cpu_quota /
validate_memory_max before it is stored, since hive-priv renders it
verbatim into the container's drop-in, and one bad value refuses the whole
set (argus on #4268, carried over from the wanted-state consumer this
replaces). A failed read, or a bucket that does not answer, leaves the
stored caps as they are; three failures in a row raise a banner.

swarm-nats-auth grants every hive `$JS.API.DIRECT.GET.KV_agent-limits.>`:
read-only, and every agent's key, not just the hive's own. Scoping it to
the hive's agents would need a roster in the auth responder, which
policy.rs argues against. mara chose this on #4268 (c95293); narrowing it
is a possible low-priority follow-up, not planned. A test pins that the
grant reads every key, writes none, and reaches no other bucket.

Contributes to #3865.
This commit is contained in:
atlas 2026-10-12 11:26:18 +02:00 • committed by mara
commit fccf2ab892
8 changed files with 320 additions and 4 deletions

View file

@ -490,6 +490,17 @@ impl Policy {
format!("$JS.API.STREAM.CREATE.{agent_status}"),
format!("$KV.{}.{hive}.*", swarm_queue_client::agent_status::BUCKET),
]);
// Reading the caps the swarm sets for the agents this hive runs
// (`swarm_queue_client::agent_limits`): a direct get, so read-only, and
// no `STREAM.INFO` because the reader skips the lookup.
//
// Every agent's key, not just this hive's: the bucket is keyed by agent
// alone, and naming one hive's agents here needs the roster
// `hive_name`'s doc argues against.
subjects.push(format!(
"$JS.API.DIRECT.GET.{}.>",
swarm_queue_client::agent_limits::STREAM
));
subjects.extend(
self.extra_hive_subjects
.iter()
@ -712,6 +723,44 @@ mod tests {
}
}
/// A hive reads any agent's caps and can change none of them, and the
/// grant reaches no other bucket.
#[test]
fn a_hive_may_read_every_agents_caps_and_write_none() {
use swarm_queue_client::agent_limits::{STREAM, direct_get_subject, key_subject};
let p = policy()
.permissions("hive-alpha")
.expect("a hive is admitted");
let may = |subject: &str| p.publish.iter().any(|g| covers(g, subject));
// Its own agents and agents running elsewhere alike.
for agent in ["atlas", "iris"] {
assert!(may(&direct_get_subject(agent)), "cannot read {agent}");
}
// A KV put or delete is a publish to the key's subject; the rest
// reshape or empty the stream.
for forbidden in [
key_subject("atlas"),
format!("$JS.API.STREAM.CREATE.{STREAM}"),
format!("$JS.API.STREAM.UPDATE.{STREAM}"),
format!("$JS.API.STREAM.DELETE.{STREAM}"),
format!("$JS.API.STREAM.PURGE.{STREAM}"),
format!("$JS.API.STREAM.MSG.DELETE.{STREAM}"),
format!("$JS.API.CONSUMER.CREATE.{STREAM}"),
] {
assert!(!may(&forbidden), "hive alpha may publish {forbidden}");
}
// The read reaches only this bucket.
for other in [
swarm_queue_client::agent_inference::direct_get_subject("atlas"),
swarm_queue_client::agent_schedules::direct_get_subject("atlas"),
"$JS.API.DIRECT.GET.KV_hive-wanted-beta.$KV.hive-wanted-beta.beta".to_owned(),
] {
assert!(!may(&other), "hive alpha may publish {other}");
}
}
#[test]
fn an_extra_subject_without_the_placeholder_is_refused() {
// 🩸 The option exists to put a second stream inside ONE hive's