notify router: deliveries log, dry-run hit counts, api and page
This commit is contained in:
parent
6dd9d0086e
commit
ed1fd053e7
24 changed files with 1848 additions and 40 deletions
|
|
@ -298,10 +298,15 @@ impl Policy {
|
|||
/// `swarm_wire::router_sink::REQUEST_REVIEWER_SUBJECT` and hear the answer
|
||||
/// on its own inbox. The controller is the subject's one subscriber and
|
||||
/// holds the forge token; no wildcard over `$SWARM.router.sink`.
|
||||
/// - **Deliveries log.** Open or create `NOTIFY_ROUTER_LOG`, publish its one
|
||||
/// subject, and read rows and the newest events by `STREAM.MSG.GET`.
|
||||
/// - **Api.** Hear the two `swarm_wire::router_api` requests and answer each
|
||||
/// once, under `allow_responses`. The controller is their one publisher.
|
||||
fn notify_router_permissions(client_id: &str) -> Permissions {
|
||||
use swarm_wire::{forge_events, notify_flows, notify_router, router_todo};
|
||||
use swarm_wire::{forge_events, notify_flows, notify_router, router_api, router_todo};
|
||||
|
||||
let events = forge_events::STREAM;
|
||||
let log_stream = router_api::LOG_STREAM;
|
||||
let consumer = forge_events::ROUTER_CONSUMER;
|
||||
let state = format!("KV_{}", notify_router::STATE_BUCKET);
|
||||
let flows = format!("KV_{}", notify_flows::BUCKET);
|
||||
|
|
@ -326,12 +331,21 @@ impl Policy {
|
|||
format!("$JS.API.DIRECT.GET.{flows}.>"),
|
||||
format!("$JS.API.STREAM.INFO.{}", Self::agent_status_stream()),
|
||||
swarm_wire::router_sink::REQUEST_REVIEWER_SUBJECT.to_owned(),
|
||||
format!("$JS.API.STREAM.MSG.GET.{events}"),
|
||||
format!("$JS.API.STREAM.INFO.{log_stream}"),
|
||||
format!("$JS.API.STREAM.CREATE.{log_stream}"),
|
||||
format!("$JS.API.STREAM.MSG.GET.{log_stream}"),
|
||||
router_api::LOG_SUBJECT.to_owned(),
|
||||
Self::inbox(client_id),
|
||||
]);
|
||||
Permissions {
|
||||
publish,
|
||||
subscribe: Some(vec![Self::inbox(client_id)]),
|
||||
responses: None,
|
||||
subscribe: Some(vec![
|
||||
router_api::DELIVERIES_SUBJECT.to_owned(),
|
||||
router_api::DRY_RUN_SUBJECT.to_owned(),
|
||||
Self::inbox(client_id),
|
||||
]),
|
||||
responses: Some(router_api::REPLY_DEADLINE),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -698,6 +712,12 @@ impl Policy {
|
|||
swarm_wire::builder::LOCK_SUBJECT.to_owned(),
|
||||
// An operator's claude login step, sent as a request the same way.
|
||||
swarm_wire::claude_login::SUBJECT.to_owned(),
|
||||
// The notification router's deliveries log and a dry run of a flows
|
||||
// document, sent as requests: the reply comes back on this client's
|
||||
// inbox. The router is their one subscriber; no other grant names
|
||||
// them.
|
||||
swarm_wire::router_api::DELIVERIES_SUBJECT.to_owned(),
|
||||
swarm_wire::router_api::DRY_RUN_SUBJECT.to_owned(),
|
||||
// Verified forge deliveries, kept in the forge events stream for
|
||||
// the notification router. This client is their one publisher: the
|
||||
// router reads them and no hive or agent grant names them.
|
||||
|
|
@ -1748,7 +1768,9 @@ mod tests {
|
|||
#[test]
|
||||
fn the_notify_router_drains_its_own_consumer_and_writes_only_its_own_state() {
|
||||
use swarm_queue_client::client_inbox_prefix;
|
||||
use swarm_wire::{forge_events, notify_flows, notify_router, router_sink, router_todo};
|
||||
use swarm_wire::{
|
||||
forge_events, notify_flows, notify_router, router_api, router_sink, router_todo,
|
||||
};
|
||||
|
||||
let p = with_router()
|
||||
.permissions(NOTIFY_ROUTER)
|
||||
|
|
@ -1771,6 +1793,13 @@ mod tests {
|
|||
format!("$JS.API.DIRECT.GET.KV_{state}.$KV.{state}.42.5571"),
|
||||
router_sink::REQUEST_REVIEWER_SUBJECT.to_owned(),
|
||||
"$JS.API.STREAM.INFO.KV_agent-status".to_owned(),
|
||||
// The deliveries log: make it, append to it, read it by sequence.
|
||||
format!("$JS.API.STREAM.CREATE.{}", router_api::LOG_STREAM),
|
||||
format!("$JS.API.STREAM.INFO.{}", router_api::LOG_STREAM),
|
||||
format!("$JS.API.STREAM.MSG.GET.{}", router_api::LOG_STREAM),
|
||||
router_api::LOG_SUBJECT.to_owned(),
|
||||
// The newest forge events, for a dry run.
|
||||
format!("$JS.API.STREAM.MSG.GET.{events}"),
|
||||
// The flows document: read it.
|
||||
format!("$JS.API.STREAM.INFO.KV_{flows}"),
|
||||
format!(
|
||||
|
|
@ -1797,6 +1826,17 @@ mod tests {
|
|||
"$KV.agent-schedules.atlas".to_owned(),
|
||||
"$SWARM.router.sink.other".to_owned(),
|
||||
"$SWARM.router.sink.>".to_owned(),
|
||||
// The log: no delete, no purge, no message delete; no read of the todo
|
||||
// stream; no other subject below the router's two prefixes; and no
|
||||
// sending of the requests it answers.
|
||||
format!("$JS.API.STREAM.DELETE.{}", router_api::LOG_STREAM),
|
||||
format!("$JS.API.STREAM.PURGE.{}", router_api::LOG_STREAM),
|
||||
format!("$JS.API.STREAM.MSG.DELETE.{}", router_api::LOG_STREAM),
|
||||
format!("$JS.API.STREAM.MSG.GET.{}", router_todo::STREAM),
|
||||
"$SWARM.router.log.other".to_owned(),
|
||||
"$SWARM.router.api.other".to_owned(),
|
||||
router_api::DRY_RUN_SUBJECT.to_owned(),
|
||||
router_api::DELIVERIES_SUBJECT.to_owned(),
|
||||
// The flows document: not write it, nor make the bucket.
|
||||
format!("$KV.{flows}.{}", notify_flows::KEY),
|
||||
format!("$JS.API.STREAM.CREATE.KV_{flows}"),
|
||||
|
|
@ -1807,8 +1847,15 @@ mod tests {
|
|||
assert!(!may(&refused), "the router may reach {refused}");
|
||||
}
|
||||
let inbox = client_inbox_prefix(NOTIFY_ROUTER);
|
||||
assert_eq!(p.subscribe, Some(vec![format!("{inbox}.>")]));
|
||||
assert_eq!(p.responses, None);
|
||||
assert_eq!(
|
||||
p.subscribe,
|
||||
Some(vec![
|
||||
router_api::DELIVERIES_SUBJECT.to_owned(),
|
||||
router_api::DRY_RUN_SUBJECT.to_owned(),
|
||||
format!("{inbox}.>"),
|
||||
])
|
||||
);
|
||||
assert_eq!(p.responses, Some(router_api::REPLY_DEADLINE));
|
||||
assert_eq!(policy().permissions(NOTIFY_ROUTER), None);
|
||||
}
|
||||
|
||||
|
|
@ -1854,6 +1901,48 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// The controller asks and the router answers: nobody else may send the
|
||||
/// two requests, and nobody else may hear them.
|
||||
#[test]
|
||||
fn only_the_reader_may_ask_the_router_and_only_the_router_may_hear_it() {
|
||||
use swarm_wire::router_api::{DELIVERIES_SUBJECT, DRY_RUN_SUBJECT, LOG_SUBJECT};
|
||||
|
||||
let p = with_router();
|
||||
let grants = [
|
||||
(
|
||||
"the reader",
|
||||
p.permissions("swarm-controller").expect("reader"),
|
||||
),
|
||||
("a hive", p.permissions("hive-alpha").expect("a hive")),
|
||||
("the router", p.permissions(NOTIFY_ROUTER).expect("router")),
|
||||
("the minter", p.permissions(CLAUDE_LOGIN).expect("minter")),
|
||||
("the builder", p.permissions(BUILDER).expect("builder")),
|
||||
(
|
||||
"the classifier",
|
||||
p.permissions(CLASSIFIER).expect("classifier"),
|
||||
),
|
||||
("atlas", p.agent_token_permissions("atlas").expect("atlas")),
|
||||
];
|
||||
for (who, grant) in &grants {
|
||||
for subject in [DELIVERIES_SUBJECT, DRY_RUN_SUBJECT] {
|
||||
let sends = grant.publish.iter().any(|g| covers(g, subject));
|
||||
let hears = may_subscribe(grant, subject);
|
||||
assert_eq!(sends, *who == "the reader", "{who} sends {subject}");
|
||||
assert_eq!(hears, *who == "the router", "{who} hears {subject}");
|
||||
}
|
||||
// The log has one writer.
|
||||
let appends = grant.publish.iter().any(|g| covers(g, LOG_SUBJECT));
|
||||
assert_eq!(appends, *who == "the router", "{who} appends to the log");
|
||||
}
|
||||
// The router answers for as long as the controller waits.
|
||||
let router = &grants[2].1;
|
||||
assert!(
|
||||
router.responses >= Some(swarm_wire::router_api::REPLY_DEADLINE),
|
||||
"{:?}",
|
||||
router.responses
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_router_may_queue_an_agents_todo_and_only_the_reader_may_keep_a_forge_event() {
|
||||
use swarm_wire::{forge_events, router_todo};
|
||||
|
|
|
|||
Loading…
Reference in a new issue