Files
mesh-delivery/modules/mesh-grants/internal/grants/keeploaded.go
T
jschoubben dead64ee6e mesh-grants: a pass composing part of the mesh keeps what the bus loaded
Issue 1966: the list sealed 2026-11-07 23:09, composed while the seat list was unread
(only the bootstrapped seats compose), took the node seats from the tool runners, the
vault's reads from the node-engines and the seat queues from the build modules, and left
out ledger.novox.mesh-knowledge.

- While the seat list is unread (the gate's MayAct with no seat) or a pass is placed by
  the haves, each user of the loaded list and of the list in force is sealed with the
  union of what the pass composes and what those lists give it (publish, subscribe,
  responses; a deny only where every list saying one denies it), kept the way a refused
  principal is; the users so kept are the pass's kept_loaded.
- In such a pass a list that still takes a permission from, or leaves out, a loaded user
  is held (user-list-held), naming each user and how many permissions it loses.
- A held module keeps its ledger account, ledger.<node>.<module>, with its other users:
  heldModuleUsers matched <node>.<module>[.<process>] only, which dropped the ledger user.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-07 23:28:17 +01:00

123 lines
4.1 KiB
Go

package grants
import (
"fmt"
"slices"
"sort"
)
// **A pass that cannot compose the whole mesh keeps what the bus loaded** (issue 1966, the registry delivery's sealed list of
// 2026-11-07 23:09). While the seat list is unread (only the bootstrapped seats compose, ADR 0636 §1) or a pass is placed by
// the node-engines' haves (Inputs.ByHaves), what the composer derives is a part of the mesh: that list sealed took the node
// seats from every tool runner, the vault's reads from the node-engines and the seat queues from the build modules. Such a
// pass composes each user of the loaded list and of the list in force as the union of what it composes and what those lists
// give it (publish, subscribe, the allowance of responses; a deny only where every list that says one denies it), so it
// only adds, the indexer's serving of the seat that reads the list included; and the list is held where it would still take
// a permission from a loaded user (holdsBack, LoadedLosses).
// keepsLoaded says why this pass keeps what the bus loaded, or "" when it composes the whole mesh.
func (in Inputs) keepsLoaded() string {
switch {
case in.ByHaves != "":
return in.ByHaves
case in.SeatListUnread:
return "the seat list is unread, so only the bootstrapped seats compose"
}
return ""
}
// KeepLoaded is, by user, the union of each user of loaded and of inForce with what composed gives it, for every such user
// composed gives less than either list (or not at all). Pure.
func KeepLoaded(composed, loaded []User, inForce map[string]User) map[string]User {
byUser := map[string]User{}
for _, u := range composed {
byUser[u.User] = u
}
others := map[string][]User{}
for _, u := range loaded {
others[u.User] = append(others[u.User], u)
}
for name, u := range inForce {
others[name] = append(others[name], u)
}
out := map[string]User{}
for name, from := range others {
c, composedHere := byUser[name]
short := !composedHere
for _, o := range from {
if len(Losses(c, o)) > 0 {
short = true
}
}
if !short {
continue
}
u := User{User: name, Publish: c.Publish, Subscribe: c.Subscribe, AllowResponses: c.AllowResponses, PublishDeny: c.PublishDeny}
denyKnown := composedHere && c.PublishDeny != nil
for _, o := range from {
u.Publish = set(append(slices.Clone(u.Publish), o.Publish...))
u.Subscribe = set(append(slices.Clone(u.Subscribe), o.Subscribe...))
u.AllowResponses = u.AllowResponses || o.AllowResponses
switch {
case o.PublishDeny == nil:
case !denyKnown:
u.PublishDeny, denyKnown = set(o.PublishDeny), true
default:
u.PublishDeny = slices.DeleteFunc(slices.Clone(u.PublishDeny), func(d string) bool { return !slices.Contains(o.PublishDeny, d) })
}
}
if u.Publish == nil {
u.Publish = []string{}
}
if u.Subscribe == nil {
u.Subscribe = []string{}
}
out[name] = u
}
return out
}
// Losses are the permissions had gives that now does not: each publish and subscribe, the allowance of responses, and
// each deny now adds where had's denies are known. Pure.
func Losses(now, had User) []string {
var out []string
for _, s := range minus(had.Publish, now.Publish) {
out = append(out, "publish "+s)
}
for _, s := range minus(had.Subscribe, now.Subscribe) {
out = append(out, "subscribe "+s)
}
if had.AllowResponses && !now.AllowResponses {
out = append(out, "responses")
}
if had.PublishDeny != nil {
for _, s := range minus(now.PublishDeny, had.PublishDeny) {
out = append(out, "deny "+s)
}
}
return out
}
// LoadedLosses names, sorted, each loaded user the list composed now (listed: its users; composed: their grants) would
// take a permission from or leave out, with how many it loses. Pure.
func LoadedLosses(listed []string, composed, loaded []User) []string {
byUser := map[string]User{}
for _, u := range composed {
byUser[u.User] = u
}
var out []string
for _, l := range loaded {
c, ok := byUser[l.User]
switch {
case !slices.Contains(listed, l.User) || !ok:
out = append(out, l.User+" (left out)")
default:
if n := len(Losses(c, l)); n > 0 {
out = append(out, fmt.Sprintf("%s (%d permissions)", l.User, n))
}
}
}
sort.Strings(out)
return slices.Compact(out)
}