From f67d15536b276ae13969f753fbd6a22f4c4b456c Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 25 Oct 2026 22:32:35 +0100 Subject: [PATCH 1/3] =?UTF-8?q?Grant=20the=20grants=20part=20publishing=20?= =?UTF-8?q?the=20modules'=20memberships,=20and=20nothing=20more=20(ADR=200?= =?UTF-8?q?547=20=C2=A75,=20issue=201945)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No membership is published since release 6 (issue 1945 notes 42, 47), so a seat-holding module placed now cannot serve its seat or pull asks (mesh-build-go on ace, 2026-10-25). The operator decided at 22:31 that the grants part publishes them from the first milestone: its principal, module mesh-grants, may publish mesh.assignment.*.* and no stream definition. The SDK's copy of this composer gets the same line, so the two stay byte-equal. Research 072 (the controller, NATS-native): this moves the memberships of its Bus row to the grants part. --- internal/broker/membership_test.go | 9 +++++++++ internal/broker/nats.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/internal/broker/membership_test.go b/internal/broker/membership_test.go index 5bb335c7..8f8e8493 100644 --- a/internal/broker/membership_test.go +++ b/internal/broker/membership_test.go @@ -72,6 +72,15 @@ func TestAnAccountMayReadItsOwnMembershipAndNoOthers(t *testing.T) { has(t, perms.Subscribe, "mesh.assignment.anchor.postgres") has(t, perms.Publish, "$JS.API.DIRECT.GET.ASSIGNMENTS.mesh.assignment.anchor.postgres") hasNot(t, perms.Subscribe, "mesh.assignment.>") + hasNot(t, perms.Publish, "mesh.assignment.*.*") + // The grants part alone publishes memberships (ADR 0547 §5), and defines no stream. + grants, err := PermissionsFor(Principal{Kind: KindModule, Node: "anchor", Module: GrantsModule, PasswordHash: "x"}) + if err != nil { + t.Fatal(err) + } + has(t, grants.Publish, "mesh.assignment.*.*") + hasNot(t, grants.Publish, "$JS.API.STREAM.CREATE.>") + hasNot(t, grants.Publish, "mesh.assignment.>") } // The runtime arriving on a machine changes nothing about what each module is issued (to-be 38 WP2): diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 66f66698..6230f12d 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -915,6 +915,11 @@ func PermissionsFor(p Principal) (Permissions, error) { sub = append(sub, BusConnectSubject, BusDisconnectSubject) pub = append(pub, BusConnzSubject) } + // The grants part alone publishes the modules' memberships into the assignments stream (novox/hq ADR 0547 §5; + // the operator, 2026-10-25 22:31): one per subject, `mesh.assignment..`, and no stream it defines. + if p.Module == GrantsModule { + pub = append(pub, "mesh.assignment.*.*") + } // The view alone reads the build seats' workers for the backlog of asks (issue 1887 A8a). if p.Module == ViewModule { pub = append(pub, BuildBacklogReads()...) @@ -1581,6 +1586,10 @@ func ComposeAccounts(principals []Principal) (string, error) { // from them. No other principal is granted the subjects below, and nothing is a wildcard over them. const WatchmanModule = "mesh-delivery-watchman" +// GrantsModule is the one module whose principal publishes the modules' memberships (novox/hq ADR 0547 §5): the grants +// part, which composes them from the manifests and placement. It defines no stream and is granted no other subject here. +const GrantsModule = "mesh-grants" + // The bus's own events, imported into MESH from the default system account `$SYS` under subjects of MESH's own // (novox/hq ADR 0431 §1, issue 1147): the server says an account's connections opening and closing only to the system // account, and the list of an account's connections only to a request made there. `$SYS` holds no user, so nobody From d01839c29c3710a69fb32d69f7f6c250266ff0a5 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 25 Oct 2026 22:51:25 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Grant=20the=20grants=20part=20reading=20eve?= =?UTF-8?q?ry=20membership;=20seal=20the=20seeded=20account=20in=20the=20a?= =?UTF-8?q?ddresses=20test=20(ADR=200547=20=C2=A75,=20review;=20issue=2019?= =?UTF-8?q?45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The grants part reads each module's last membership before it publishes, so it is granted the direct get on every membership subject beside the publish (review of mesh-grants, B1). The test TestTheControlPlaneIsToldWhereTheNodePutTheStoreAndTheBroker failed on main since PR #467: it seeds anchor.mesh-controller with no seal, and an account counts as issued only once sealed (ADR 0543 §2). The rule is right; the fixture is fixed, sealing the seeded credential the controller already uses. Test only. --- cmd/mesh-controller/addresses_integration_test.go | 5 +++++ internal/broker/membership_test.go | 2 ++ internal/broker/nats.go | 5 +++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/mesh-controller/addresses_integration_test.go b/cmd/mesh-controller/addresses_integration_test.go index 363f05e1..1420e0f6 100644 --- a/cmd/mesh-controller/addresses_integration_test.go +++ b/cmd/mesh-controller/addresses_integration_test.go @@ -111,6 +111,11 @@ func TestTheControlPlaneIsToldWhereTheNodePutTheStoreAndTheBroker(t *testing.T) Kind: inventory.BusController, Node: "anchor", Module: "mesh-controller"}, "bootstrap"); err != nil { t.Fatal(err) } + // Seeded with the credential the controller already uses, so it is sealed: an account counts as issued only once + // sealed (ADR 0543 §2, PR #467), and genesis's seed is the credential in use. + if err := open.inventory.MarkBusAccountSealed(ctx, "anchor.mesh-controller"); err != nil { + t.Fatal(err) + } if _, err := assign(ctx, open, "anchor", "mesh-controller"); err != nil { t.Fatal(err) } diff --git a/internal/broker/membership_test.go b/internal/broker/membership_test.go index 8f8e8493..a837340c 100644 --- a/internal/broker/membership_test.go +++ b/internal/broker/membership_test.go @@ -79,6 +79,8 @@ func TestAnAccountMayReadItsOwnMembershipAndNoOthers(t *testing.T) { t.Fatal(err) } has(t, grants.Publish, "mesh.assignment.*.*") + has(t, grants.Publish, "$JS.API.DIRECT.GET.ASSIGNMENTS.mesh.assignment.*.*") + hasNot(t, perms.Publish, "$JS.API.DIRECT.GET.ASSIGNMENTS.mesh.assignment.*.*") hasNot(t, grants.Publish, "$JS.API.STREAM.CREATE.>") hasNot(t, grants.Publish, "mesh.assignment.>") } diff --git a/internal/broker/nats.go b/internal/broker/nats.go index 6230f12d..8d51e7c3 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -916,9 +916,10 @@ func PermissionsFor(p Principal) (Permissions, error) { pub = append(pub, BusConnzSubject) } // The grants part alone publishes the modules' memberships into the assignments stream (novox/hq ADR 0547 §5; - // the operator, 2026-10-25 22:31): one per subject, `mesh.assignment..`, and no stream it defines. + // the operator, 2026-10-25 22:31): one per subject, `mesh.assignment..`, reading the last one of each + // first so it publishes only what changed, and no stream it defines. if p.Module == GrantsModule { - pub = append(pub, "mesh.assignment.*.*") + pub = append(pub, "mesh.assignment.*.*", "$JS.API.DIRECT.GET."+AssignmentsStream+".mesh.assignment.*.*") } // The view alone reads the build seats' workers for the backlog of asks (issue 1887 A8a). if p.Module == ViewModule { From a81717e53cf43dcb64454717f5301fc6c6c8c90e Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 25 Oct 2026 23:30:20 +0100 Subject: [PATCH 3/3] Seal the seeded account in the adapter-input test, the second fixture of its kind (issue 1945, PR #467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kind: a test that seeds a bus account without sealing it, which an account counted as issued only once sealed (ADR 0543 §2) no longer counts. The whole integration level, run locally, found two: the addresses test (fixed before) and TestTheAdaptersInputCarriesTheOwnSecretsTheBoxesTheLicencesAndTheBus. Test only. --- .../compose_input_credentials_integration_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/mesh-controller/compose_input_credentials_integration_test.go b/cmd/mesh-controller/compose_input_credentials_integration_test.go index 411ea852..09ca3614 100644 --- a/cmd/mesh-controller/compose_input_credentials_integration_test.go +++ b/cmd/mesh-controller/compose_input_credentials_integration_test.go @@ -217,6 +217,11 @@ func TestTheAdaptersInputCarriesTheOwnSecretsTheBoxesTheLicencesAndTheBus(t *tes Module: "objects"}, "a-password"); err != nil { t.Fatal(err) } + // Minted and sealed to its node: an account counts as issued, and is named among the minted, only once sealed + // (ADR 0543 §2, PR #467). + if err := inv.MarkBusAccountSealed(ctx, "anchor.objects"); err != nil { + t.Fatal(err) + } if err := inv.PutBusMembership(ctx, "laptop", "sealed-membership"); err != nil { t.Fatal(err) }