Merge pull request 'mesh-grants writes its memberships bare through the mesh-sdk codec (issue 774)' (#114) from fix/memberships-shape into main
Reviewed-on: #114
This commit was merged in pull request #114.
This commit is contained in:
@@ -3,7 +3,7 @@ module mesh-grants
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030143623-83cf97b690f5
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0 h1:lKU3HjNXGiVbLW2hUgFjU/TMTUYMlNnnmZxG5T7biQA=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030143623-83cf97b690f5 h1:hyyaB1rU7uzlQZvWAxZ9P6pL/4LCFpOVXMRgn9F7fic=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030001116-2da0de75c3f0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030143623-83cf97b690f5/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package manifest
|
package manifest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -23,6 +25,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
"git.novox.be/novox/mesh-sdk/go/health"
|
"git.novox.be/novox/mesh-sdk/go/health"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
"git.novox.be/novox/mesh-sdk/go/outward"
|
"git.novox.be/novox/mesh-sdk/go/outward"
|
||||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||||
|
|
||||||
@@ -1296,7 +1299,7 @@ func TestGrants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ms, _ := p.MembershipOf("a", "mod-x")
|
ms, _ := p.MembershipOf("a", "mod-x")
|
||||||
if mo, _ := ms.(map[string]any); !strings.Contains(fmt.Sprint(mo["published"]), "last_pass") || mo["composed"] == nil {
|
if mo, _ := ms.(map[string]any); !strings.Contains(fmt.Sprint(mo["published"]), "membership:") || mo["last_pass"] == nil || mo["composed"] == nil {
|
||||||
t.Errorf("membership = %v", ms)
|
t.Errorf("membership = %v", ms)
|
||||||
}
|
}
|
||||||
gr, _ := p.Grants("mod-x")
|
gr, _ := p.Grants("mod-x")
|
||||||
@@ -1727,7 +1730,7 @@ func TestGrants(t *testing.T) {
|
|||||||
return m
|
return m
|
||||||
}, true, "", ""},
|
}, true, "", ""},
|
||||||
{"a change that drops verified-sender its holder still asks for is not published, and named", verified, false, "channel telegram verified-sender of a/mod-x", telegramClaim},
|
{"a change that drops verified-sender its holder still asks for is not published, and named", verified, false, "channel telegram verified-sender of a/mod-x", telegramClaim},
|
||||||
{"a change that drops verified-sender its holder no longer asks for is published", verified, true, `"published":"published"`, `[{"name":"channel","scope":"mesh","kind":"telegram"}]`},
|
{"a change that drops verified-sender its holder no longer asks for is published", verified, true, "published:published", `[{"name":"channel","scope":"mesh","kind":"telegram"}]`},
|
||||||
{"a change that takes the last holder from a seat a placed module claims is not published, the seat named", func(m map[string]any) map[string]any {
|
{"a change that takes the last holder from a seat a placed module claims is not published, the seat named", func(m map[string]any) map[string]any {
|
||||||
m["seats"] = []any{map[string]any{"seat": brokerName, "verb": brokerVerb, "subject": "mesh.seat." + brokerName + ".tool." + brokerVerb}}
|
m["seats"] = []any{map[string]any{"seat": brokerName, "verb": brokerVerb, "subject": "mesh.seat." + brokerName + ".tool." + brokerVerb}}
|
||||||
return m
|
return m
|
||||||
@@ -1755,25 +1758,38 @@ func TestGrants(t *testing.T) {
|
|||||||
b.stream = map[string][]byte{"mesh.assignment." + key: raw}
|
b.stream = map[string][]byte{"mesh.assignment." + key: raw}
|
||||||
b.seqs = map[string]uint64{"mesh.assignment." + key: 4}
|
b.seqs = map[string]uint64{"mesh.assignment." + key: 4}
|
||||||
}
|
}
|
||||||
res := partOn(b).Compose()
|
res := partOn(b).Pass()
|
||||||
if got := slices.Contains(res.Published, key); got != row.published {
|
if got := slices.Contains(res.Published, key); got != row.published {
|
||||||
t.Fatalf("published %v, not published %v", res.Published, res.NotPublished)
|
t.Fatalf("published %v, not published %v", res.Published, res.NotPublished)
|
||||||
}
|
}
|
||||||
|
// The state holds the bare membership the assignments stream carries, through mesh-sdk's codec, and
|
||||||
|
// only one published (issue 774); what became of it is the kept pass's, which grants_membership reads.
|
||||||
|
state, _, inState, _ := b.ownGet(grants.StateMemberships, key)
|
||||||
|
ms, _ := partOn(b).MembershipOf("a", "mod-x")
|
||||||
|
pass := fmt.Sprint(ms.(map[string]any)["last_pass"])
|
||||||
if row.published {
|
if row.published {
|
||||||
if row.names == "" && row.last != nil && !strings.Contains(string(b.stream["mesh.assignment."+key]), "10.0.0.1") {
|
if row.names == "" && row.last != nil && !strings.Contains(string(b.stream["mesh.assignment."+key]), "10.0.0.1") {
|
||||||
t.Errorf("the private network of the last membership is not carried: %s", b.stream["mesh.assignment."+key])
|
t.Errorf("the private network of the last membership is not carried: %s", b.stream["mesh.assignment."+key])
|
||||||
}
|
}
|
||||||
if raw, _, _, _ := b.ownGet(grants.StateMemberships, key); !strings.Contains(string(raw), row.names) {
|
got, err := membership.DecodeGrantsMembership(state)
|
||||||
t.Errorf("the pass does not name %q: %s", row.names, raw)
|
var sent membership.Membership
|
||||||
|
_ = json.Unmarshal(b.stream["mesh.assignment."+key], &sent)
|
||||||
|
if err != nil || !reflect.DeepEqual(got, sent) || bytes.Contains(state, []byte(`"membership"`)) {
|
||||||
|
t.Errorf("the state is not the bare membership published (%v): %s", err, state)
|
||||||
|
}
|
||||||
|
if !strings.Contains(pass, row.names) {
|
||||||
|
t.Errorf("the pass does not name %q: %s", row.names, pass)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if inState {
|
||||||
|
t.Errorf("a membership held back reached the state: %s", state)
|
||||||
|
}
|
||||||
said := strings.Join(res.NotPublished, "\n")
|
said := strings.Join(res.NotPublished, "\n")
|
||||||
if !strings.Contains(said, grants.ShadowDifference) || !strings.Contains(said, row.names) {
|
if !strings.Contains(said, grants.ShadowDifference) || !strings.Contains(said, row.names) {
|
||||||
t.Errorf("the loss is not named: %s", said)
|
t.Errorf("the loss is not named: %s", said)
|
||||||
}
|
}
|
||||||
ms, _ := partOn(b).MembershipOf("a", "mod-x")
|
if !strings.Contains(pass, row.names) {
|
||||||
if !strings.Contains(fmt.Sprint(ms), row.names) {
|
|
||||||
t.Errorf("grants_membership does not name what would be lost: %v", ms)
|
t.Errorf("grants_membership does not name what would be lost: %v", ms)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -106,8 +106,12 @@ type Result struct {
|
|||||||
MembershipsWritten int `json:"memberships_written"`
|
MembershipsWritten int `json:"memberships_written"`
|
||||||
Published []string `json:"published,omitempty"`
|
Published []string `json:"published,omitempty"`
|
||||||
NotPublished []string `json:"not_published,omitempty"`
|
NotPublished []string `json:"not_published,omitempty"`
|
||||||
Assertions int `json:"assertions"`
|
// Retired is, by `<node>.<module>`, the seat verbs its last published membership served that mesh-sdk's seat
|
||||||
AssertionsWritten int `json:"assertions_written"`
|
// catalogue no longer has (Membership.Retired): the pass keeps it, since the state memberships holds the bare
|
||||||
|
// membership alone (issue 774).
|
||||||
|
Retired map[string][]string `json:"retired,omitempty"`
|
||||||
|
Assertions int `json:"assertions"`
|
||||||
|
AssertionsWritten int `json:"assertions_written"`
|
||||||
// Asserted is each module whose declared streams this pass created, `<module> at <digest>: created …`, and
|
// Asserted is each module whose declared streams this pass created, `<module> at <digest>: created …`, and
|
||||||
// NotAsserted each creation refused, `<module> <stream>: <why>` (issue 1790).
|
// NotAsserted each creation refused, `<module> <stream>: <why>` (issue 1790).
|
||||||
Asserted []string `json:"asserted,omitempty"`
|
Asserted []string `json:"asserted,omitempty"`
|
||||||
@@ -320,9 +324,19 @@ func (p *Part) Compose() Result {
|
|||||||
case m.Published != MembershipUnchanged:
|
case m.Published != MembershipUnchanged:
|
||||||
res.NotPublished = append(res.NotPublished, key+": "+m.Published)
|
res.NotPublished = append(res.NotPublished, key+": "+m.Published)
|
||||||
}
|
}
|
||||||
data, _ := json.Marshal(m)
|
if len(m.Retired) > 0 {
|
||||||
if p.changed(&res, StateMemberships, key, data) {
|
if res.Retired == nil {
|
||||||
res.MembershipsWritten++
|
res.Retired = map[string][]string{}
|
||||||
|
}
|
||||||
|
res.Retired[key] = m.Retired
|
||||||
|
}
|
||||||
|
// The state holds the bare membership as the assignments stream carries it, through mesh-sdk's one codec (issue
|
||||||
|
// 774): only one published now or unchanged, so a membership held back never reaches a reader by this way. What
|
||||||
|
// became of each at this pass is the pass's own (Published, NotPublished, Retired), kept with it (Pass).
|
||||||
|
if now || m.Published == MembershipUnchanged {
|
||||||
|
if p.changed(&res, StateMemberships, key, membership.EncodeGrantsMembership(m.Membership)) {
|
||||||
|
res.MembershipsWritten++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
held, heldErr := HeldStreams(p.Call)
|
held, heldErr := HeldStreams(p.Call)
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The part's tools, as its manifest lists them. Module tools in this milestone: a `mesh-*` seat must be in mesh-sdk's own
|
// The part's tools, as its manifest lists them. Module tools in this milestone: a `mesh-*` seat must be in mesh-sdk's own
|
||||||
@@ -133,16 +135,52 @@ func (p *Part) MembershipOf(node, module string) (any, error) {
|
|||||||
}
|
}
|
||||||
out["composed"] = why
|
out["composed"] = why
|
||||||
}
|
}
|
||||||
if raw, rev, found, err := p.Store.Get(StateMemberships, node+"."+module); err != nil {
|
// The state holds the bare membership last published (mesh-sdk's codec, issue 774); what became of it at the last
|
||||||
|
// pass is the pass's, kept in user-list at KeyPass.
|
||||||
|
key := node + "." + module
|
||||||
|
if raw, rev, found, err := p.Store.Get(StateMemberships, key); err != nil {
|
||||||
out["published"] = "not readable: " + err.Error()
|
out["published"] = "not readable: " + err.Error()
|
||||||
} else if found {
|
} else if !found {
|
||||||
out["published"] = map[string]any{"revision": rev, "last_pass": json.RawMessage(raw)}
|
out["published"] = "no pass of the part's process has published it yet"
|
||||||
|
} else if was, err := membership.DecodeGrantsMembership(raw); err != nil {
|
||||||
|
out["published"] = fmt.Sprintf("revision %d is not readable: %v", rev, err)
|
||||||
} else {
|
} else {
|
||||||
out["published"] = "no pass of the part's process has composed it yet"
|
out["published"] = map[string]any{"revision": rev, "digest": digestOf(was), "membership": was}
|
||||||
}
|
}
|
||||||
|
out["last_pass"] = p.passOf(key)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// passOf is what the last pass the part's process kept says of one membership: published, unchanged or why not, and
|
||||||
|
// the seat verbs it dropped as retired.
|
||||||
|
func (p *Part) passOf(key string) any {
|
||||||
|
raw, rev, found, err := p.Store.Get(StateUserList, KeyPass)
|
||||||
|
if err != nil {
|
||||||
|
return "not readable: " + err.Error()
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return "no pass of the part's process has been kept yet"
|
||||||
|
}
|
||||||
|
var res Result
|
||||||
|
if err := json.Unmarshal(raw, &res); err != nil {
|
||||||
|
return "not readable: " + err.Error()
|
||||||
|
}
|
||||||
|
said := MembershipUnchanged
|
||||||
|
if slices.Contains(res.Published, key) {
|
||||||
|
said = MembershipPublished
|
||||||
|
}
|
||||||
|
for _, n := range res.NotPublished {
|
||||||
|
if k, why, _ := strings.Cut(n, ": "); k == key {
|
||||||
|
said = why
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := map[string]any{"revision": rev, "published": said}
|
||||||
|
if r := res.Retired[key]; len(r) > 0 {
|
||||||
|
out["retired"] = r
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// Held answers every membership the part's process held back at its last pass, each `<node>.<module>` with why (what
|
// Held answers every membership the part's process held back at its last pass, each `<node>.<module>` with why (what
|
||||||
// publishing would lose), from the pass it keeps (issue 1945).
|
// publishing would lose), from the pass it keeps (issue 1945).
|
||||||
func (p *Part) Held() (any, error) {
|
func (p *Part) Held() (any, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user