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
123 lines
4.1 KiB
Go
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)
|
|
}
|