Files
mesh-delivery/modules/mesh-grants/internal/grants/keeploaded.go
T
jschoubben 7bc4836eb0 AM-D3: a runtime whose tool runner does not honour the mark is denied every marked subject
The grants part follows the assignment half, reads each node's reading and the
runner its node-engine says running, and where the runner does not state
approval-mark/1 or the reading is not the running runner's, composes the
subscribe deny of every marked tool and verb the runtime carries, named in
grants_user_list's withheld and answered by the seat's verb withheld.

Builds: design 46 7 §6 (the deny's subjects, its cases, withheld in grants_user_list and the verb withheld), design 47 G §195, §309, lead answer A-203 (the assignment half followed by revision and assignment-revision; withheld's forms, keyed by node in the user list's state as the lead confirmed)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 06:17:59 +01:00

260 lines
8.3 KiB
Go

package grants
import (
"encoding/json"
"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, and every deny any of them says), 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.WantsUnread != "":
return in.WantsUnread
case in.CatalogUnread != "":
return in.CatalogUnread
case in.AssignmentUnread != "":
return in.AssignmentUnread
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
}
// Every deny any list says is kept: a deny the composer drops while it composes part of the mesh would widen the
// user (the composer gives every composed user a deny, empty or not, so an intersection would drop the list in
// force's).
u := User{User: name, Publish: c.Publish, Subscribe: c.Subscribe, AllowResponses: c.AllowResponses, PublishDeny: c.PublishDeny,
SubscribeDeny: c.SubscribeDeny}
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
if o.PublishDeny != nil || u.PublishDeny != nil {
u.PublishDeny = set(append(slices.Clone(u.PublishDeny), o.PublishDeny...))
}
if o.SubscribeDeny != nil || u.SubscribeDeny != nil {
u.SubscribeDeny = set(append(slices.Clone(u.SubscribeDeny), o.SubscribeDeny...))
}
}
if u.Publish == nil {
u.Publish = []string{}
}
if u.Subscribe == nil {
u.Subscribe = []string{}
}
out[name] = u
}
return out
}
// Losses are how now differs from had where had's word is lost: each publish and subscribe had gives that now does not,
// the allowance of responses, and, where had's denies are known, each deny now adds and each deny of had now drops (a deny
// dropped widens the user past what had held it to). 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")
}
// A subscribe deny now adds is a narrowing and no word of had lost (design 46 approval mark §6); one had held that now
// drops widens the user past it.
for _, s := range minus(had.SubscribeDeny, now.SubscribeDeny) {
out = append(out, "subscribe deny dropped "+s)
}
if had.PublishDeny != nil {
for _, s := range minus(now.PublishDeny, had.PublishDeny) {
out = append(out, "deny "+s)
}
for _, s := range minus(had.PublishDeny, now.PublishDeny) {
out = append(out, "deny dropped "+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)
}
// MembershipNarrows names what the membership last published (was) gives that the one composed now does not, field by field
// (`<field> <value>`), the private network, the contributions received and the bindings aside, which the pass carries over
// or the bus module writes; none when now covers was. Both are memberships as JSON. Pure.
func MembershipNarrows(was, now []byte) []string {
var a, b map[string]any
if json.Unmarshal(was, &a) != nil || json.Unmarshal(now, &b) != nil {
return nil
}
var out []string
for _, field := range sortedKeys(a) {
switch field {
case "mesh", "receives", "bindings":
continue
}
out = append(out, uncovered(field, a[field], b[field])...)
}
return out
}
// uncovered names each part of a that b does not hold: a key of an object by key, an element of a list by its value.
func uncovered(path string, a, b any) []string {
switch av := a.(type) {
case map[string]any:
bv, _ := b.(map[string]any)
var out []string
for _, k := range sortedKeys(av) {
out = append(out, uncovered(path+"."+k, av[k], bv[k])...)
}
return out
case []any:
bv, _ := b.([]any)
var out []string
for _, e := range av {
ej, _ := json.Marshal(e)
if !slices.ContainsFunc(bv, func(x any) bool { xj, _ := json.Marshal(x); return string(xj) == string(ej) }) {
out = append(out, path+" "+string(ej))
}
}
return out
}
aj, _ := json.Marshal(a)
bj, _ := json.Marshal(b)
if string(aj) != string(bj) {
return []string{path + " " + string(aj)}
}
return nil
}
// UnionMembership is the membership last published (was) grown by the one composed now: every list of both, deduplicated, was's
// elements first and then what now adds; an object by key; a single value as now says it. The private network, the contributions
// received and the bindings are now's, which carry them over or the bus module writes. A pass composing part of the mesh publishes
// it, so it only grows (issue 1966): a membership kept whole could never receive what the pass that ends the hold needs. It also
// names, as MembershipNarrows does, what was has that now lacks. Pure.
func UnionMembership(was, now []byte) ([]byte, []string, error) {
var a, b map[string]any
if err := json.Unmarshal(was, &a); err != nil {
return nil, nil, err
}
if err := json.Unmarshal(now, &b); err != nil {
return nil, nil, err
}
out := map[string]any{}
for k, v := range a {
out[k] = v
}
for k, v := range b {
switch k {
case "mesh", "receives", "bindings":
out[k] = v
default:
out[k] = unionOf(a[k], v)
}
}
for _, k := range []string{"mesh", "receives", "bindings"} {
if _, ok := b[k]; !ok {
delete(out, k)
}
}
body, err := json.Marshal(out)
return body, MembershipNarrows(was, now), err
}
func unionOf(a, b any) any {
switch bv := b.(type) {
case map[string]any:
av, ok := a.(map[string]any)
if !ok {
return b
}
out := map[string]any{}
for k, v := range av {
out[k] = v
}
for k, v := range bv {
out[k] = unionOf(av[k], v)
}
return out
case []any:
av, ok := a.([]any)
if !ok {
return b
}
out := slices.Clone(av)
seen := map[string]bool{}
for _, e := range out {
j, _ := json.Marshal(e)
seen[string(j)] = true
}
for _, e := range bv {
if j, _ := json.Marshal(e); !seen[string(j)] {
seen[string(j)] = true
out = append(out, e)
}
}
return out
}
return b
}