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.
This commit is contained in:
atlas 2026-08-17 20:48:52 +02:00
commit e4a98d40ba

View file

@ -223,6 +223,16 @@ impl Policy {
// `store.keys()` — the controller lists before it fetches, so a // `store.keys()` — the controller lists before it fetches, so a
// reader without this can get a key it already knows and discover // reader without this can get a key it already knows and discover
// nothing. // 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}.>"), format!("$JS.API.CONSUMER.CREATE.{stream}.>"),
]); ]);
subjects subjects
@ -356,6 +366,17 @@ mod tests {
let p = policy() let p = policy()
.permissions("swarm-controller") .permissions("swarm-controller")
.expect("the reader is admitted"); .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!( assert!(
p.publish p.publish
.contains(&"$JS.API.CONSUMER.CREATE.KV_hive-status.>".to_owned()) .contains(&"$JS.API.CONSUMER.CREATE.KV_hive-status.>".to_owned())