From e4a98d40baa0fef748c0b17dc0e7bd2732a0bd6c Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 17 Aug 2026 20:48:52 +0200 Subject: [PATCH] fix(#3363): grant the bare consumer-create subject the reader actually uses `store.keys()` creates an ephemeral ordered consumer, whose create subject ends at the stream name. `>` matches one or more tokens and never zero, so the `.>` form alone never covered it: the server refused `$JS.API.CONSUMER.CREATE.KV_hive-status`, the refusal reached the client as a timeout, and the operator saw a 503 on the hive status page. The test asserted only the `.>` form, which reads as covering the bare one, so the suite stayed green while every list timed out in production. It now names the bare subject separately and first. --- swarm-nats-auth/src/policy.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/swarm-nats-auth/src/policy.rs b/swarm-nats-auth/src/policy.rs index bf58f70d..4f3a76b2 100644 --- a/swarm-nats-auth/src/policy.rs +++ b/swarm-nats-auth/src/policy.rs @@ -223,6 +223,16 @@ impl Policy { // `store.keys()` — the controller lists before it fetches, so a // reader without this can get a key it already knows and discover // nothing. + // + // BOTH forms, and the bare one is the one that matters. `keys()` + // creates an **ephemeral** ordered consumer, whose create subject + // carries no consumer name — and `>` matches one or more tokens, + // never zero, so the `.>` form alone does not cover it. Granting + // only that produced `Permissions Violation for Publish to + // "$JS.API.CONSUMER.CREATE.KV_hive-status"`, which reaches the + // client as a **timeout** and the operator as a 503. The `.>` form + // stays for a named/durable consumer. + format!("$JS.API.CONSUMER.CREATE.{stream}"), format!("$JS.API.CONSUMER.CREATE.{stream}.>"), ]); subjects @@ -356,6 +366,17 @@ mod tests { let p = policy() .permissions("swarm-controller") .expect("the reader is admitted"); + // The BARE subject, asserted separately and first: `keys()` creates an + // ephemeral consumer, so its create subject ends at the stream name, + // and `>` matches one or more tokens rather than zero. This assertion + // used to name only the `.>` form below — which reads as covering the + // bare one and does not, so the suite was green while every list timed + // out in production. + assert!( + p.publish + .contains(&"$JS.API.CONSUMER.CREATE.KV_hive-status".to_owned()), + "an ephemeral consumer create carries no name token" + ); assert!( p.publish .contains(&"$JS.API.CONSUMER.CREATE.KV_hive-status.>".to_owned())