diff --git a/swarm-nats-auth/src/policy.rs b/swarm-nats-auth/src/policy.rs index ae5900c3..48e94651 100644 --- a/swarm-nats-auth/src/policy.rs +++ b/swarm-nats-auth/src/policy.rs @@ -189,8 +189,8 @@ impl Policy { /// no such per-hive split at the `STREAM.INFO`/`STREAM.CREATE` layer /// — the stream itself, not a slice of it, is what every hive's /// `open_or_create` needs to reach before it can publish to its own - /// `hive-notices.` subject (which `extra_hive_subjects` already - /// covers, since that one *is* per-hive). Granting these two subjects + /// `hive-notices.` subject, which [`Self::hive_subjects`] grants + /// beside these from the same crate constant. Granting these two subjects /// to every hive is therefore correct, not a widening: it is /// `CREATE`/`INFO` on one named stream, the same shape already /// measured safe for the hive-status bucket in [`Self::create`] — @@ -233,6 +233,11 @@ impl Policy { format!("$KV.{}.{hive}", self.bucket), ]); subjects.extend(Self::notices_subjects()); + // The hive's own slice of the stream the subjects above let it open. + // Named from the defining crate so a rename cannot leave the grant + // pointing at a subject nobody publishes to. Not `extra_hive_subjects`: + // that is for streams this crate does not know about. + subjects.push(swarm_queue_client::notices::subject(hive)); subjects.extend( self.extra_hive_subjects .iter() @@ -544,6 +549,27 @@ mod tests { ); } + #[test] + fn a_hive_may_publish_to_its_own_notices_subject() { + // Opening the stream and writing into it are separate grants. + let p = policy() + .permissions("hive-alpha") + .expect("a hive is admitted"); + assert!(p.publish.contains(&"hive-notices.alpha".to_owned())); + } + + #[test] + fn a_hive_may_not_publish_to_another_hives_notices_subject() { + // The stream-level subjects are identical for every hive; this one + // must not be, or reaching the stream would carry writing to every + // other hive's slice of it. + let p = policy() + .permissions("hive-alpha") + .expect("a hive is admitted"); + assert!(!p.publish.contains(&"hive-notices.beta".to_owned())); + assert!(!p.publish.iter().any(|s| s == "hive-notices.>")); + } + #[test] fn the_notices_stream_grant_is_identical_across_hives() { // Unlike `$KV..` or an `extra_hive_subjects`