DLV-F4r: the grants part takes the views follower from mesh-sdk (go/views); the held-seat rows use a seat whose verbs its holder declares

mesh-broker's verbs are all optional now and the composition grants its reading verbs to its
holder, so taking one from a membership loses no holder: the rows that prove a lost last holder
is held back name a seat the catalogue defines without verbs, claimed by the bus module beside
mesh-broker.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 12:41:36 +01:00
parent d78161f579
commit f271a70659
6 changed files with 24 additions and 20 deletions
@@ -18,11 +18,11 @@ import (
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-delivery/sdk/views"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/membership"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/seal"
"git.novox.be/novox/mesh-sdk/go/views"
"mesh-grants/internal/grants"
)
+1 -1
View File
@@ -36,7 +36,7 @@ import (
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-delivery/sdk/views"
"git.novox.be/novox/mesh-sdk/go/views"
"mesh-grants/internal/grants"
)
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
"git.novox.be/novox/mesh-delivery/sdk/views"
"git.novox.be/novox/mesh-sdk/go/views"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/handacts"
-3
View File
@@ -9,7 +9,6 @@ require (
)
require (
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/klauspost/compress v1.20.0 // indirect
@@ -21,5 +20,3 @@ require (
golang.org/x/sys v0.48.0 // indirect
golang.org/x/time v0.15.0 // indirect
)
replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk
+20 -13
View File
@@ -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 {
@@ -10,8 +10,8 @@ import (
"sync"
"time"
"git.novox.be/novox/mesh-delivery/sdk/views"
"git.novox.be/novox/mesh-sdk/go/asks"
"git.novox.be/novox/mesh-sdk/go/views"
)
// **The grants part's first views** (novox/hq ADR 0615 §1, §4 to §6, ADR 0621, ADR 0622; design 25 § 12.2; DLV-F4r,