Watch
0
0
Fork
You've already forked hyperhive
0

notify router: deliveries log, dry-run hit counts, api and page

This commit is contained in:
atlas 2026-10-24 22:13:07 +02:00
commit ed1fd053e7
24 changed files with 1848 additions and 40 deletions

View file

@ -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};