Watch
0
0
Fork
You've already forked hyperhive
0

review classifier: queued review requests survive a restart and are never refused

This commit is contained in:
atlas 2026-10-21 22:21:24 +02:00
commit d6aa83146a
7 changed files with 1021 additions and 454 deletions

View file

@ -664,6 +664,25 @@ impl Policy {
subjects
}
/// The review requests the controller queues and takes
/// (`swarm_wire::review_classifier::REQUEST_STREAM`): publishing one on
/// its PR's subject, creating the controller's durable consumer, pulling
/// from it and acking what it pulled. `STREAM.INFO.*` and
/// `STREAM.CREATE.*` open the stream. The controller is the stream's one
/// publisher, so no other grant may name its subjects. A refused publish
/// reaches the client as a timeout.
fn review_request_subjects() -> [String; 4] {
use swarm_wire::review_classifier::{
REQUEST_CONSUMER, REQUEST_STREAM, REQUEST_SUBJECT_PREFIX,
};
[
format!("{REQUEST_SUBJECT_PREFIX}.>"),
format!("$JS.API.CONSUMER.CREATE.{REQUEST_STREAM}.{REQUEST_CONSUMER}"),
format!("$JS.API.CONSUMER.MSG.NEXT.{REQUEST_STREAM}.{REQUEST_CONSUMER}"),
format!("$JS.ACK.{REQUEST_STREAM}.{REQUEST_CONSUMER}.>"),
]
}
fn reader_subjects(&self) -> Vec<String> {
let stream = self.stream();
let mut subjects = Self::jetstream_minimum().to_vec();
@ -774,6 +793,7 @@ impl Policy {
swarm_wire::subagent_term::consumer_create_grant(),
"$KV.*.>".to_owned(),
]);
subjects.extend(Self::review_request_subjects());
// The per-agent status bucket, read-side only. Same five subjects as
// the hive-status block above and for the same measured reasons —
// including BOTH `CONSUMER.CREATE` forms, since `keys()` builds an
@ -1594,6 +1614,56 @@ mod tests {
}
}
/// Queueing a review request, taking it through the controller's durable
/// consumer and acking it are the reader's, and nobody else's.
#[test]
fn only_the_reader_may_queue_and_take_review_requests() {
use swarm_wire::review_classifier::{
REQUEST_CONSUMER, REQUEST_STREAM, REQUEST_SUBJECT_PREFIX,
};
let subjects = [
format!("{REQUEST_SUBJECT_PREFIX}.hyperhive.hyperhive.5219"),
format!("$JS.API.STREAM.INFO.{REQUEST_STREAM}"),
format!("$JS.API.STREAM.CREATE.{REQUEST_STREAM}"),
format!("$JS.API.CONSUMER.CREATE.{REQUEST_STREAM}.{REQUEST_CONSUMER}"),
format!("$JS.API.CONSUMER.MSG.NEXT.{REQUEST_STREAM}.{REQUEST_CONSUMER}"),
format!("$JS.ACK.{REQUEST_STREAM}.{REQUEST_CONSUMER}.1.4.4.1761000000000000000.0"),
];
let p = with_classifier()
.with_agent_token_subjects(vec!["$SWARM.term.{agent}".to_owned()])
.expect("a per-agent template is valid");
let reader = p.permissions("swarm-controller").expect("the reader");
for subject in &subjects {
assert!(
reader.publish.iter().any(|g| covers(g, subject)),
"the reader may not publish {subject}"
);
}
for (who, grant) in [
("a hive", p.permissions("hive-alpha").expect("a hive")),
(
"the classifier",
p.permissions(CLASSIFIER).expect("admitted"),
),
(
"an agent",
p.agent_token_permissions("atlas").expect("configured"),
),
] {
for subject in &subjects {
assert!(
!grant.publish.iter().any(|g| covers(g, subject)),
"{who} may publish {subject}: {:?}",
grant.publish
);
}
}
// The consumer grants name the one consumer.
let other = format!("$JS.API.CONSUMER.MSG.NEXT.{REQUEST_STREAM}.another");
assert!(!reader.publish.iter().any(|g| covers(g, &other)));
}
const BUILDER: &str = "swarm-builder";
#[test]