Since 16:39 the running assigner cannot answer its wants page within a call's bound, and the failed wants read held the whole user list, so no new build got its credential. As the assignments view already does, an unanswered wants view now composes what runs from each have's fingerprint, is said as a gap that is no failed read, and keeps the pass to one that only adds (keepsLoaded), so the list is held only where it would take from a loaded user. Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
247 lines
7.8 KiB
Go
247 lines
7.8 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.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}
|
|
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 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")
|
|
}
|
|
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
|
|
}
|