The tool runner reads its membership from the grants part's state alone: no stream read, no subject heard (ADR 0571 §10)

Follow no longer asks the ASSIGNMENTS stream for a copy nor subscribes mesh.assignment; a module with no membership key is
said so and served in its derived shape. The test mesh issues memberships where the grants part keeps them.
Tests: none added; the stream-fallback assertions of two existing tests are replaced by their state-only counterparts, and
the test mesh no longer makes the stream. The two integration tests of the bus were run locally against the embedded bus.

(issue 774, 774/S32)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 00:25:19 +01:00
parent 75943ca7b0
commit 4fbfdfad13
4 changed files with 56 additions and 131 deletions
+20 -18
View File
@@ -86,11 +86,10 @@ func New(t *testing.T) *Mesh {
t.Fatal(err)
}
js, _ := nc.JetStream()
for _, s := range []string{"ASSIGNMENTS", "EVENTS"} {
_ = js.DeleteStream(s)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "ASSIGNMENTS", Subjects: []string{"mesh.assignment.>"},
MaxMsgsPerSubject: 1, AllowDirect: true}); err != nil {
_ = js.DeleteStream("EVENTS")
// The grants part's state memberships, where a runtime reads what is issued it (ADR 0571 clause 10).
_ = js.DeleteKeyValue(buscompose.GrantsMembershipsBucket)
if _, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: buscompose.GrantsMembershipsBucket, History: 1}); err != nil {
t.Fatal(err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "EVENTS", Subjects: []string{"mesh.mod.*.event.>"}}); err != nil {
@@ -100,20 +99,11 @@ func New(t *testing.T) *Mesh {
return &Mesh{nc: nc, js: js}
}
// Issue publishes a membership.
// Issue writes a membership where the grants part keeps it, its state memberships, key `<node>.<module>` (novox/hq ADR
// 0571 clause 7 and 10; mesh-sdk buscompose.GrantsMembershipsBucket), the one place a runtime reads it. It writes with
// mesh-sdk's encoder, the grants part's own, never json of this package's type: a value built here by hand agreed with
// the reader's mistake and hid that the part wrote another shape (issue 774).
func (m *Mesh) Issue(t *testing.T, mem bus.Membership) {
t.Helper()
body, _ := json.Marshal(mem)
if _, err := m.js.Publish(bus.MembershipSubject(mem.Node, mem.Module), body); err != nil {
t.Fatal(err)
}
}
// IssueInGrants writes a membership where the grants part keeps it, its state memberships, key `<node>.<module>`
// (novox/hq ADR 0571 clause 7; mesh-sdk buscompose.GrantsMembershipsBucket), making the bucket when it is not there.
// It writes with mesh-sdk's encoder, the grants part's own, never json of this package's type: a value built here by
// hand agreed with the reader's mistake and hid that the part wrote another shape (issue 774).
func (m *Mesh) IssueInGrants(t *testing.T, mem bus.Membership) {
t.Helper()
m.putGrants(t, mem, membership.EncodeGrantsMembership(sdkMembership(t, mem)))
}
@@ -319,6 +309,18 @@ func (m *Mesh) Emit(t *testing.T, subject string, body any) {
}
}
// Publish says body on a plain subject, as a core publish with no stream behind it.
func (m *Mesh) Publish(t *testing.T, subject string, body any) {
t.Helper()
data, _ := json.Marshal(body)
if err := m.nc.Publish(subject, data); err != nil {
t.Fatal(err)
}
if err := m.nc.Flush(); err != nil {
t.Fatal(err)
}
}
// Pending is what a module's consumer still holds: delivered and not acknowledged, and not yet
// delivered.
func (m *Mesh) Pending(t *testing.T, node, module string) (ackPending, notDelivered uint64) {