|
|
|
@@ -21,12 +21,12 @@ import (
|
|
|
|
|
|
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
|
|
|
|
|
|
"git.novox.be/novox/mesh-delivery/sdk/views"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/views"
|
|
|
|
|
"mesh-grants/internal/buscompose"
|
|
|
|
|
|
|
|
|
|
"mesh-grants/internal/grants"
|
|
|
|
@@ -378,14 +378,18 @@ const (
|
|
|
|
|
|
|
|
|
|
// brokerSeat is one verb of the bus's seat as a membership serves it: nats claims the seat in fixture and no membership
|
|
|
|
|
// composed there serves it, so a membership or list that drops it leaves the seat with no holder (issue 1945).
|
|
|
|
|
func brokerSeat() (seat, verb string) {
|
|
|
|
|
s, _ := catalogue.SeatNamed(catalogue.BrokerSeat)
|
|
|
|
|
verb = "v"
|
|
|
|
|
if len(s.Serves) > 0 {
|
|
|
|
|
verb = s.Serves[0].Name
|
|
|
|
|
}
|
|
|
|
|
return s.Name, verb
|
|
|
|
|
}
|
|
|
|
|
// heldSeat is a seat the fixture's bus module claims beside mesh-broker (natsHeldManifest), and a verb of it: a seat the
|
|
|
|
|
// catalogue defines without verbs, its holder declaring them, so a verb the composition no longer grants is lost, never
|
|
|
|
|
// retired. mesh-broker's own verbs are all optional, and the composition grants its reading verbs to every holder, so
|
|
|
|
|
// losing one of them loses no holder.
|
|
|
|
|
func heldSeat() (seat, verb string) { return heldSeatName, "v" }
|
|
|
|
|
|
|
|
|
|
// heldSeatName is that seat, and natsHeldManifest the fixture's bus module claiming it beside mesh-broker.
|
|
|
|
|
const (
|
|
|
|
|
heldSeatName = "mesh-dns-resolver"
|
|
|
|
|
natsHeldManifest = `{"module":"nats","version":"1","claims":[{"name":"mesh-broker"},{"name":"` + heldSeatName + `"}],` +
|
|
|
|
|
`"own-secrets":{"broker":"/var/lib/nats/broker"}}`
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// fixture is a mesh of two machines: a runs the bus and mod-x, b runs mod-y; the bus node's agent loaded a list.
|
|
|
|
|
func fixture() *fakeBus {
|
|
|
|
@@ -1561,8 +1565,9 @@ func TestGrants(t *testing.T) {
|
|
|
|
|
// Issue 1945, 2026-10-27 09:21: a list that would leave a seat the list in force holds with no holder,
|
|
|
|
|
// while a placed module still claims it, is held, as such a membership is: not written, the warning
|
|
|
|
|
// raised naming the seat. A list that only adds is written; one whose loaded grants cannot be read is held.
|
|
|
|
|
b.manifestOf("nats", natsHeldManifest)
|
|
|
|
|
inForce := slices.Clone(b.loaded)
|
|
|
|
|
seat, verb := brokerSeat()
|
|
|
|
|
seat, verb := heldSeat()
|
|
|
|
|
seatGrant := "mesh.seat." + seat + ".call." + verb + ".*"
|
|
|
|
|
for i := range b.loaded {
|
|
|
|
|
if b.loaded[i].User == "a.nats" {
|
|
|
|
@@ -1627,7 +1632,7 @@ func TestGrants(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
t.Run("memberships: published unless a seat would lose its last holder or a holder a capability it asks for", func(t *testing.T) {
|
|
|
|
|
const key = "a.mod-x"
|
|
|
|
|
brokerName, brokerVerb := brokerSeat()
|
|
|
|
|
brokerName, brokerVerb := heldSeat()
|
|
|
|
|
// The telegram module's claim of its channel, as mesh-catalog's telegram manifest has it (c05aa5e6), on mod-x.
|
|
|
|
|
const telegramClaim = `[{"name":"channel","scope":"mesh","kind":"telegram","capabilities":["deliver","verified-sender"]}]`
|
|
|
|
|
verified := func(m map[string]any) map[string]any {
|
|
|
|
@@ -1684,6 +1689,7 @@ func TestGrants(t *testing.T) {
|
|
|
|
|
} {
|
|
|
|
|
t.Run(row.name, func(t *testing.T) {
|
|
|
|
|
b := fixture()
|
|
|
|
|
b.manifestOf("nats", natsHeldManifest)
|
|
|
|
|
if row.claims != "" {
|
|
|
|
|
b.manifestOf("mod-x", strings.Replace(xManifest, `"own-secrets"`, `"claims":`+row.claims+`,"own-secrets"`, 1))
|
|
|
|
|
}
|
|
|
|
@@ -1885,6 +1891,7 @@ func TestGrants(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
t.Run("memberships held back: every one is said and listed", func(t *testing.T) {
|
|
|
|
|
b := fixture()
|
|
|
|
|
b.manifestOf("nats", natsHeldManifest)
|
|
|
|
|
b.loaded = shadowOf(t, b)
|
|
|
|
|
recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{}))
|
|
|
|
|
b.own[grants.StateMemberships] = map[string][]byte{}
|
|
|
|
@@ -1892,7 +1899,7 @@ func TestGrants(t *testing.T) {
|
|
|
|
|
raw, _ := json.Marshal(grants.Memberships(recs)[key].Membership)
|
|
|
|
|
var m map[string]any
|
|
|
|
|
_ = json.Unmarshal(raw, &m)
|
|
|
|
|
seat, verb := brokerSeat()
|
|
|
|
|
seat, verb := heldSeat()
|
|
|
|
|
m["seats"] = []any{map[string]any{"seat": seat, "verb": verb, "subject": "mesh.seat." + seat + ".tool." + verb}}
|
|
|
|
|
raw, _ = json.Marshal(m)
|
|
|
|
|
b.own[grants.StateMemberships][key] = raw
|
|
|
|
@@ -1918,7 +1925,7 @@ func TestGrants(t *testing.T) {
|
|
|
|
|
<-done
|
|
|
|
|
held, err := partOn(b).MembershipOf("", "")
|
|
|
|
|
h, _ := held.(map[string]any)
|
|
|
|
|
if seat, _ := brokerSeat(); err != nil || h["count"] != 2 || !strings.Contains(fmt.Sprint(h["held"]), "the last holder of "+seat) {
|
|
|
|
|
if seat, _ := heldSeat(); err != nil || h["count"] != 2 || !strings.Contains(fmt.Sprint(h["held"]), "the last holder of "+seat) {
|
|
|
|
|
t.Errorf("grants_membership with neither node nor module = %v, %v", held, err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := partOn(b).MembershipOf("a", ""); err == nil {
|
|
|
|
|