Files
mesh-delivery/modules/mesh-grants/internal/grants/held.go
T

146 lines
5.8 KiB
Go

package grants
import (
"fmt"
"slices"
"sort"
"strings"
"mesh-grants/internal/buscompose"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// **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>` or `<node>.<module>.<process>` (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
for _, user := range candidates {
if user == prefix || strings.HasPrefix(user, prefix+".") {
out[user] = fmt.Sprintf("the manifest of %s is held: %s", module, 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 := asks.Condition{Key: HeldModuleCondition, Subject: asks.Scope{Scope: "module", ID: "mesh-grants"}}
if why == "" {
c.State = asks.StateCleared
} else {
c.Severity, c.State = asks.SeverityWarning, asks.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 = asks.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
}
}