Refuse a held-back membership's old wrapper in the tool runner, pinning mesh-sdk's review-round-1 codec
mesh/delivery merged: source.main moved to novox/mesh-tools@0edcd2501dae
mesh/delivery-group group fix/memberships-shape merged: every member is merged
mesh/check checking ac645e2caedd on 3c9068f99a43 | novox/mesh-tools#49 in group fix/memberships-shape

The old grants part wrote its wrapper for a held-back membership too; the new part leaves such a key alone, and taking
it would serve what the hold kept back with the stream's copy ignored. The integration test now proves that copy is
served instead. Review round 1 of issue 774's fix.
This commit is contained in:
2026-10-30 15:38:40 +01:00
parent a479a90fb2
commit ac645e2cae
5 changed files with 27 additions and 9 deletions
+3 -2
View File
@@ -470,8 +470,9 @@ func (c *Conn) followGrants(module string) {
return // a membership withdrawn is the stream's, or the module's unassignment's, to say
}
// Read through mesh-sdk's one codec of this state, the grants part's writer's (issue 774): it takes the bare
// membership and the wrapper the part wrote before, and refuses a value naming no node or module, which then
// leaves the assignments stream's copy in force instead of an empty membership.
// membership and a wrapper the part wrote before of one published or unchanged, and refuses a held-back one's
// wrapper or a value naming no node or module, which then leaves the assignments stream's copy in force instead.
// A refused value arriving after a good one keeps the last good membership: nothing here clears it.
var m Membership
body, err := membership.BareGrantsMembership(e.Value())
if err == nil {
+17 -1
View File
@@ -125,7 +125,13 @@ func TestAMembershipTheGrantsPartWroteWrappedIsServed(t *testing.T) {
mesh.Bucket(t, "gitea_repos")
gitea := mt.MembershipOf("gitea", "anchor", false, nil)
gitea.State = []bus.StateIssued{{Name: "repos", Bucket: "gitea_repos", Writes: true}}
mesh.IssueInGrantsWrapped(t, gitea)
mesh.IssueInGrantsWrapped(t, gitea, "published")
// The old wrapper of a membership the part held back carries its unpublished composition: refused, so the
// assignments stream's copy is served, never what the hold kept back.
pages := mt.MembershipOf("pages", "anchor", false, nil)
mesh.Issue(t, pages)
heldBack := mt.MembershipOf("pages", "anchor", true, nil)
mesh.IssueInGrantsWrapped(t, heldBack, "not published, a shadow difference: publishing would lose the last holder of x")
conn, err := bus.Connect(bus.Credential{URL: mt.URL(t), Module: "node-tools", Node: "anchor"})
if err != nil {
t.Fatal(err)
@@ -148,6 +154,16 @@ func TestAMembershipTheGrantsPartWroteWrappedIsServed(t *testing.T) {
if e, err := conn.StateGet("gitea", "repos", "novox.hq"); err != nil || e == nil || string(e.Value) != `{"v":1}` {
t.Errorf("the state reads back %+v, %v", e, err)
}
conn.Follow("pages")
mt.Until(t, func() error {
if m := conn.Membership("pages"); m == nil || len(m.Serves) == 0 {
return fmt.Errorf("pages' membership is %+v, want the assignments stream's", m)
}
return nil
})
if got, want := fmt.Sprint(conn.ServedOn("pages", "pages_list")), "[{mesh.mod.pages.tool.pages_list.anchor }]"; got != want {
t.Errorf("a held-back membership's wrapper is served: pages_list on %s, want the stream's %s", got, want)
}
}
// owedWatcher is the gitea module's view of the checker's owed: the bucket, and a runtime connection