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
+8 -32
View File
@@ -161,11 +161,11 @@ func TestAModuleHearsEveryStreamItsMembershipNames(t *testing.T) {
return nil
})
// The membership as the grants part keeps it (ADR 0571 clause 7): read first, applied live, and the assignments
// stream's copy passed over once the state has given one.
// The membership as the grants part keeps it (ADR 0571 clause 7, 10): read first and applied live, and nothing
// published on the controller's subject is heard.
granted := reader
granted.Hears = []bus.HeardIssued{{Stream: "MAIL", Consumer: "from-grants"}}
mesh.IssueInGrants(t, granted)
mesh.Issue(t, granted)
again, err := bus.Connect(bus.Credential{URL: mt.URL(t), Module: "node-tools", Node: "anchor"})
if err != nil {
t.Fatal(err)
@@ -183,45 +183,21 @@ func TestAModuleHearsEveryStreamItsMembershipNames(t *testing.T) {
t.Fatalf("the membership read at the follow names %q, want the grants part's", got)
}
granted.Hears[0].Consumer = "from-grants-2"
mesh.IssueInGrants(t, granted)
mesh.Issue(t, granted)
mt.Until(t, func() error {
if got := consumerOf(); got != "from-grants-2" {
return fmt.Errorf("the membership names %q, want the grants part's change", got)
}
return nil
})
// A later, different copy on the assignments stream changes nothing once the state has given one.
arrived := make(chan struct{}, 1)
probe, err := bus.Connect(bus.Credential{URL: mt.URL(t), Module: "probe", Node: "elsewhere"})
if err != nil {
t.Fatal(err)
}
probe.Logf = func(string, ...any) {}
t.Cleanup(probe.Close)
stopProbe, err := probe.Raw(bus.MembershipSubject("anchor", "reader"), func(string, []byte) []byte {
select {
case arrived <- struct{}{}:
default:
}
return nil
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(stopProbe)
probe.Flush()
// 774/S32/AC1: a copy said on the controller's subject (mesh.assignment) changes nothing; there is no stream to read it from.
streamed := reader
streamed.Hears = []bus.HeardIssued{{Stream: "MAIL", Consumer: "from-stream"}}
mesh.Issue(t, streamed)
select {
case <-arrived:
case <-time.After(5 * time.Second):
t.Fatal("the stream's copy was not published")
}
streamed.Hears = []bus.HeardIssued{{Stream: "MAIL", Consumer: "from-subject"}}
mesh.Publish(t, "mesh.assignment.anchor.reader", streamed)
again.Flush()
time.Sleep(200 * time.Millisecond)
if got := consumerOf(); got != "from-grants-2" {
t.Fatalf("the stream's copy was applied over the grants part's: the membership names %q", got)
t.Fatalf("a membership said on the controller's subject was applied: the membership names %q", got)
}
}