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
155 lines
6.5 KiB
Go
155 lines
6.5 KiB
Go
package grants
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"mesh-grants/internal/buscompose"
|
|
|
|
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
|
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
|
)
|
|
|
|
// **A module whose manifest waits or is refused keeps its bus users** (clause 3 of the record on a release's Approve; issue
|
|
// 774). The indexer holds a manifest it refuses, or that waits for a judge that knows a field of it, out of the manifests
|
|
// the part composes from (inputs.go, Refused), so a module with no want naming a build had no manifest to derive its users
|
|
// from and its users left the list: the bus module's accounts among them, which locked its tools out. Now such a module's
|
|
// users, the ones the list in force carries, stay in the list as it holds them, the same way a refused principal's do
|
|
// (refused.go), and the part says so as the warning mesh-grants.module-held until the manifest is accepted. One module's
|
|
// manifest never holds the list: every other principal is composed as ever. A module whose want names a build is composed
|
|
// from the manifest of that build (Inputs.Running), not kept.
|
|
|
|
// HeldModuleCondition is the key of the warning raised while a module's manifest is held and its users are kept.
|
|
const HeldModuleCondition = "mesh-grants.module-held"
|
|
|
|
// heldModuleUsers are, by user, why it is kept: the users of candidates (the ones the list in force and the bus carry) that
|
|
// belong to a module placed on a node whose manifest the indexer holds (in.Refused) and whose want names no build. A user of
|
|
// a module on a node is `<node>.<module>`, `<node>.<module>.<process>` or its ledger's `ledger.<node>.<module>`
|
|
// (buscompose.Principal.Username). Pure.
|
|
func heldModuleUsers(in Inputs, candidates []string) map[string]string {
|
|
held := map[string]string{}
|
|
for _, g := range in.Refused {
|
|
if module, ok := strings.CutPrefix(g.Input, "manifest of "); ok {
|
|
held[module] = g.Why
|
|
}
|
|
}
|
|
out := map[string]string{}
|
|
if len(held) == 0 {
|
|
return out
|
|
}
|
|
for node, modules := range in.Assigned {
|
|
for _, module := range modules {
|
|
why, ok := held[module]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, runs := in.Running[node+"."+module]; runs {
|
|
continue
|
|
}
|
|
prefix := node + "." + module
|
|
// No manifest of the module can be read at all (the trunk's is refused and no running build's is found): named
|
|
// unreadable, in the condition and in grants_user_list (ADR 0614 §8).
|
|
state := "held"
|
|
if _, readable := in.Held[module]; !readable {
|
|
state = "unreadable"
|
|
}
|
|
for _, user := range candidates {
|
|
// And its ledger account, `ledger.<node>.<module>` (issue 1966: dropped from the list sealed 2026-11-07 23:09).
|
|
if user == prefix || strings.HasPrefix(user, prefix+".") || user == "ledger."+prefix {
|
|
out[user] = fmt.Sprintf("the manifest of %s is %s: %s", module, state, why)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// runnerUsersKept are, by user, why it is kept: on a node where no placed module's manifest says it is the tool runner
|
|
// (`tool-runner`), the user of the list in force that is that node's tool runner, known by what runs (it may say the runner
|
|
// is alive, buscompose.ToolsAliveSubject, which the runner alone is granted), never by a module's name (ADR 0601 §4). A
|
|
// manifest that does not yet say what shared code no longer infers keeps the runner as it runs, said in the held module's
|
|
// condition, and is never composed without it. Pure.
|
|
func runnerUsersKept(recs buscompose.Records, inForce map[string]User) map[string]string {
|
|
out := map[string]string{}
|
|
for node, placed := range recs.Assigned {
|
|
if slices.ContainsFunc(placed, func(d buscompose.Declared) bool { return d.ToolRunner }) {
|
|
continue
|
|
}
|
|
alive := buscompose.ToolsAliveSubject(node)
|
|
for user, u := range inForce {
|
|
if strings.HasPrefix(user, node+".") && slices.Contains(u.Publish, alive) {
|
|
out[user] = fmt.Sprintf("no manifest placed on %s says it is the tool runner (tool-runner), so the tool runner keeps "+
|
|
"what it runs until its manifest says so", node)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// inForceGrants are the grants of each user of the list in force as the part sealed them, or nil where it kept none.
|
|
func (p *Part) inForceGrants() map[string]User {
|
|
if g, _, had := p.Sealed(); had && g.Digest != "" && p.Store != nil {
|
|
inForce, _ := p.sealedGrantsOf(g.Digest)
|
|
return inForce
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// mergedRefusals is refused and held in one map, for the parts that keep a user's grants of the list in force.
|
|
func mergedRefusals(refused, held map[string]string) map[string]string {
|
|
if len(held) == 0 {
|
|
return refused
|
|
}
|
|
out := make(map[string]string, len(refused)+len(held))
|
|
for u, why := range held {
|
|
out[u] = why
|
|
}
|
|
for u, why := range refused {
|
|
out[u] = why
|
|
}
|
|
return out
|
|
}
|
|
|
|
// sayHeldModules raises HeldModuleCondition while a held module's users are kept, naming each user with what became of it,
|
|
// and clears it once none is; said once per set, again when it changes, and at the first pass after a start.
|
|
func (p *Part) sayHeldModules(held map[string]string, kept map[string]User) {
|
|
if p.Say == nil {
|
|
return
|
|
}
|
|
var lines []string
|
|
users := make([]string, 0, len(held))
|
|
for u := range held {
|
|
users = append(users, u)
|
|
}
|
|
sort.Strings(users)
|
|
for _, user := range users {
|
|
became := "not in the list in force and left out"
|
|
if _, ok := kept[user]; ok {
|
|
became = "stays in the list as the list in force holds it"
|
|
}
|
|
lines = append(lines, user+" ("+became+"): "+held[user])
|
|
}
|
|
why := strings.Join(lines, "; ")
|
|
p.holdMu.Lock()
|
|
defer p.holdMu.Unlock()
|
|
if p.heldModulesKnown && why == p.heldModulesSaid {
|
|
return
|
|
}
|
|
c := oc.Condition{Key: HeldModuleCondition, Subject: oc.ConditionSubject{Scope: "module", ID: "mesh-grants"}}
|
|
if why == "" {
|
|
c.State = occ.StateCleared
|
|
} else {
|
|
c.Severity, c.State = occ.SeverityWarning, occ.StateRaised
|
|
c.Summary = fmt.Sprintf("%d bus user(s) of a module whose manifest is held are kept in the list: %s", len(users), why)
|
|
c.Headline, c.Needs = "A module's manifest is held, its bus users are kept", "a manifest the indexer accepts, or a judge that knows its fields"
|
|
c.Explanation = occ.NeedsYou + "a manifest the indexer accepts (a refused one mended, or a waiting one judged by an indexer that knows its fields). " +
|
|
"The rest of the user list is written as ever; a held module's users stay as the list in force holds them: " + why
|
|
c.Resolved = "every module's manifest is accepted"
|
|
}
|
|
if p.Say(c) == nil {
|
|
p.heldModulesSaid, p.heldModulesKnown = why, true
|
|
}
|
|
}
|