Files
mesh-host/internal/seatwait/held.go
T
jschoubben ac3422f914 H-17: the node-engine's own name is read from its applied record or first composing
Discovery, the finding writer and the vault login names read it; no fixed name.

Builds: design 47 G §527, §634 (A-149)
2026-11-12 04:58:28 +01:00

110 lines
4.1 KiB
Go

// HeldKeeper keeps the node-engine's finding `module-held`: a module the node leaves out of its pass because its
// claim cannot be held (its seat's definition missing, removed or refused, a claim another holder keeps, a credential the
// vault refuses) is no silent leave-out. The finding names the node, the module and why, and the condition keeper keeps the
// condition (novox/hq ADR 0564 §2) until the module is composed again or is no longer wanted on the node, when the finding
// is cleared (design 47, *A claim problem holds its module, never the node's pass*; test 55).
package seatwait
import (
"context"
"fmt"
"sort"
"strings"
"time"
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
)
// HeldKind is the finding of a held module; its id is `<module>.<node>`.
const HeldKind = "module-held"
// HeldKeeper writes one finding for each module the node holds, and clears it when the module is no longer held. What it
// has written is what its writer holds (design 47 G A-29, A-38, A-40): the findings an earlier run of the engine left are
// seeded into it at its Start, so a module held then and composed while the engine was away loses its condition at the
// first Reconcile, and the keeper keeps no set of its own.
type HeldKeeper struct {
Node string
Observer string
// ObserverOf, when set, answers the observer at each write (see Waiter).
ObserverOf func() string
Writer Writer
// Ctx is the run's context for the writer's acts; nil is the background.
Ctx context.Context
// Now is the clock; nil is time.Now.
Now func() time.Time
// Say tells the node's journal; nil says nothing.
Say func(string)
}
// HeldID is the finding's id for a module on a node.
func HeldID(module, node string) string { return module + "." + node }
// Reconcile makes the findings those of held (each module held and why): one is written for a module not yet written or
// held for other words, and one is cleared for a module no longer held, composed or no longer wanted here. A write or a
// clear that fails is said and tried again at the next Reconcile.
func (k *HeldKeeper) Reconcile(held map[string]string) {
ctx := k.Ctx
if ctx == nil {
ctx = context.Background()
}
written := map[string]condseat.Finding{} // by module, what the writer holds of this node's held modules
for _, f := range k.Writer.Held() {
if f.Kind != HeldKind || f.Scope != scopeModule {
continue
}
if module, ok := strings.CutSuffix(f.ID, "."+k.Node); ok {
written[module] = f
}
}
names := make([]string, 0, len(written))
for m := range written {
names = append(names, m)
}
sort.Strings(names)
for _, m := range names {
if _, still := held[m]; still {
continue
}
if err := k.Writer.Clear(ctx, scopeModule, written[m].ID, HeldKind, "the module is composed again or is no longer wanted on the node"); err != nil {
k.say(fmt.Sprintf("the finding that %s is held on %s cannot be cleared: %v", m, k.Node, err))
}
}
names = names[:0]
for m := range held {
names = append(names, m)
}
sort.Strings(names)
for _, m := range names {
why := held[m]
if was, ok := written[m]; ok && strings.HasSuffix(was.Summary, ": "+why) {
continue
}
id := HeldID(m, k.Node)
f := condseat.Finding{Observer: observerOf(k.Observer, k.ObserverOf), Kind: HeldKind, Scope: scopeModule, ID: id, Severity: condseat.FindingSeverityWarning,
Source: HeldKind,
Subject: condseat.Subject{Scope: scopeModule, ID: id, Machine: k.Node},
Summary: fmt.Sprintf("%s is held on %s: %s", m, k.Node, why),
Headline: fmt.Sprintf("%s is held on %s", m, k.Node),
Explanation: "The node leaves this module out of its pass, keeps what runs of it and composes and applies every other module. The finding clears when the module is composed again or is no longer wanted on the node.",
At: k.now()}
if err := k.Writer.Write(ctx, f); err != nil {
k.say(fmt.Sprintf("the finding that %s is held on %s cannot be written: %v", m, k.Node, err))
continue
}
k.say(f.Summary)
}
}
func (k *HeldKeeper) now() time.Time {
if k.Now != nil {
return k.Now()
}
return time.Now()
}
func (k *HeldKeeper) say(s string) {
if k.Say != nil {
k.Say(s)
}
}