review classifier: queued review requests survive a restart and are never refused
This commit is contained in:
parent
22256893fc
commit
d6aa83146a
7 changed files with 1021 additions and 454 deletions
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue