From 64d154d9d7d30abcf87ea51e17ae206c04d8cf89 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 01:16:46 +0200 Subject: [PATCH 1/3] A machine may bind its consumer and hear the answer Binding to a consumer asks the server about it and hears the answer on the client's inbox; hearing a declaration acknowledges it. A machine's user was granted none of that and was refused the first time one dialled a permissioned server: "this node cannot read its declarations". Its inbox is its own prefix, which the host now sets. --- internal/broker/nats.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 3c18343..c2ee807 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -244,8 +244,16 @@ func PermissionsFor(p Principal) (Permissions, error) { case KindNode: // A host publishes its own node's control traffic and subscribes its own declaration — // and nothing of any other node's. - pub = []string{"mesh.control." + p.Node + ".>"} - sub = []string{"mesh.node." + p.Node + ".declare"} + // And what the host does with its consumer, nothing more: binding to it asks the server + // about it (CONSUMER.INFO) and hears the answer on its own inbox; hearing a declaration + // acknowledges it. Found the first time a machine dialled a permissioned server: refused + // for both, and "this node cannot read its declarations" (2026-09-28). + pub = []string{ + "mesh.control." + p.Node + ".>", + "$JS.API.CONSUMER.INFO.NODES." + p.Node, + "$JS.ACK.NODES." + p.Node + ".>", + } + sub = []string{"mesh.node." + p.Node + ".declare", p.inbox()} case KindModule: // 1. Its own namespace: it publishes its events there and serves its tools there. Nothing -- 2.54.0 From aa2d0b51eacdde32504523b884bf7ba373f071d3 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 01:17:15 +0200 Subject: [PATCH 2/3] Golden: a machine's user may bind its consumer, ack, and hear its inbox --- internal/broker/testdata/composed.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/broker/testdata/composed.conf b/internal/broker/testdata/composed.conf index f64b2e8..b93abf6 100644 --- a/internal/broker/testdata/composed.conf +++ b/internal/broker/testdata/composed.conf @@ -32,8 +32,8 @@ accounts { subscribe: { allow: ["_INBOX.enrol.one.>"] } } } { user: "node.one", password: "$2a$11$nnnnnnnnnnnnnnnnnnnnnn", permissions: { - publish: { allow: ["$JS.ACK.NODES.one.>", "mesh.control.one.>"] } - subscribe: { allow: ["_INBOX.node.one.>", "mesh.node.one.declare"] } + publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.INFO.NODES.one", "mesh.control.one.>"] } + subscribe: { allow: ["_INBOX.node.one.>", "_INBOX.node.one.>", "mesh.node.one.declare"] } } } { user: "one.telegram", password: "$2a$11$tttttttttttttttttttttt", permissions: { publish: { allow: ["$JS.ACK.EVENTS.one_telegram.>", "mesh.seat.telegram-sender.event.delivered", "mesh.seat.telegram-sender.event.failed"] } -- 2.54.0 From 2c2eb51878b3dc76bed80c555456e5e9207bee2a Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 01:17:40 +0200 Subject: [PATCH 3/3] Only CONSUMER.INFO was missing from a machine's grants; the rest was already there --- internal/broker/nats.go | 11 +++++------ internal/broker/testdata/composed.conf | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/broker/nats.go b/internal/broker/nats.go index c2ee807..00830c7 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -244,16 +244,15 @@ func PermissionsFor(p Principal) (Permissions, error) { case KindNode: // A host publishes its own node's control traffic and subscribes its own declaration — // and nothing of any other node's. - // And what the host does with its consumer, nothing more: binding to it asks the server - // about it (CONSUMER.INFO) and hears the answer on its own inbox; hearing a declaration - // acknowledges it. Found the first time a machine dialled a permissioned server: refused - // for both, and "this node cannot read its declarations" (2026-09-28). + // And binding to its consumer, which asks the server about it (CONSUMER.INFO) — the one + // thing the host does that nothing granted. Found the first time a machine dialled a + // permissioned server: "this node cannot read its declarations" (2026-09-28). The ack and + // the inbox are granted below with every principal's. pub = []string{ "mesh.control." + p.Node + ".>", "$JS.API.CONSUMER.INFO.NODES." + p.Node, - "$JS.ACK.NODES." + p.Node + ".>", } - sub = []string{"mesh.node." + p.Node + ".declare", p.inbox()} + sub = []string{"mesh.node." + p.Node + ".declare"} case KindModule: // 1. Its own namespace: it publishes its events there and serves its tools there. Nothing diff --git a/internal/broker/testdata/composed.conf b/internal/broker/testdata/composed.conf index b93abf6..d764044 100644 --- a/internal/broker/testdata/composed.conf +++ b/internal/broker/testdata/composed.conf @@ -32,8 +32,8 @@ accounts { subscribe: { allow: ["_INBOX.enrol.one.>"] } } } { user: "node.one", password: "$2a$11$nnnnnnnnnnnnnnnnnnnnnn", permissions: { - publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.INFO.NODES.one", "mesh.control.one.>"] } - subscribe: { allow: ["_INBOX.node.one.>", "_INBOX.node.one.>", "mesh.node.one.declare"] } + publish: { allow: ["$JS.ACK.NODES.one.>", "$JS.API.CONSUMER.INFO.NODES.one", "mesh.control.one.>"] } + subscribe: { allow: ["_INBOX.node.one.>", "mesh.node.one.declare"] } } } { user: "one.telegram", password: "$2a$11$tttttttttttttttttttttt", permissions: { publish: { allow: ["$JS.ACK.EVENTS.one_telegram.>", "mesh.seat.telegram-sender.event.delivered", "mesh.seat.telegram-sender.event.failed"] } -- 2.54.0