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 `.` or `..` (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 { if user == prefix || strings.HasPrefix(user, 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 } }