Watch
0
0
Fork
You've already forked hyperhive
0

swarm: grant the controller STREAM.MSG.GET; list subagents concurrently

GET /api/agents/{name}/subagents reads each subagent's newest row with
get_last_raw_message_by_subject, which publishes
$JS.API.STREAM.MSG.GET.term-sub-<agent>. The controller's NATS grant had
no STREAM.MSG.GET, so every lookup was refused and waited the 10s request
timeout, one subagent after another; six subagents passed nginx's 60s.

Grant `$JS.API.STREAM.MSG.GET.*`. A wildcard matches a whole subject
token only, so a `term-sub-*` spelling would name one stream literally;
`*` is the narrowest grant that covers the per-agent streams, the same
width as the controller's STREAM.INFO.* grant.

The lookups now run concurrently (16 at a time) with a 2s timeout each.
A lookup that fails or times out lists its subagent with a null
last_row_unix, and one warn per request names how many failed and the
first error.
This commit is contained in:
atlas 2026-10-04 19:30:20 +02:00 • committed by mara
commit d81fb2236f
3 changed files with 76 additions and 26 deletions

View file

@ -540,6 +540,14 @@ impl Policy {
"$JS.API.STREAM.INFO.*".to_owned(),
"$JS.API.STREAM.CREATE.*".to_owned(),
"$JS.API.DIRECT.GET.*.>".to_owned(),
// The newest row of one subagent in `term-sub-<agent>`, which the
// subagent list reads by subject. Those streams do not allow
// direct get, so `DIRECT.GET` above does not reach them. `*` is a
// whole token: `term-sub-*` would name one stream literally, and
// the agent is not known here. Also reaches every other stream's
// `MSG.GET`, the same width as `STREAM.INFO.*` above. A refused
// publish reaches the client as a timeout.
"$JS.API.STREAM.MSG.GET.*".to_owned(),
"$KV.*.>".to_owned(),
]);
// The per-agent status bucket, read-side only. Same five subjects as
@ -1171,6 +1179,25 @@ mod tests {
assert!(p.publish.contains(&"$JS.API.STREAM.CREATE.*".to_owned()));
}
#[test]
fn the_controller_may_read_a_subagents_newest_row() {
let p = policy()
.permissions("swarm-controller")
.expect("a reader is admitted");
assert!(p.publish.contains(&"$JS.API.STREAM.MSG.GET.*".to_owned()));
// `*` only matches a whole token, so a prefix spelling grants one
// stream literally named that and reads nothing.
assert!(!p.publish.iter().any(|s| s.contains("term-sub-*")));
// Control: a hive's grant holds no `MSG.GET`, so the subject above is
// the controller's alone.
assert!(
!policy()
.hive_subjects("alpha")
.iter()
.any(|s| s.starts_with("$JS.API.STREAM.MSG.GET"))
);
}
#[test]
fn the_notices_stream_grant_is_identical_across_hives() {
// Unlike `$KV.<bucket>.<hive>` or an `extra_hive_subjects`