ADR 0590 clause 3 ends the choice of a condition source by the keeper's take-over key, which mesh-sdk no longer defines. The assigner watched that key and said every condition straight to the router while it named the controller. It now writes each as a finding the keeper carries, and says one straight only where it has no findings bucket at all. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
105 lines
4.0 KiB
Go
105 lines
4.0 KiB
Go
package assign
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
)
|
|
|
|
// The assigner's said conditions become findings (novox/hq ADR 0564 §2, issue 1952 WM6, note 16): every condition it says
|
|
// under its own name (ADR 0437 §9) — a hold and its lift, a node failing alone, a module on neither list, a backup, a pair
|
|
// placed by one source, the seat waiting for its states — is also a finding in its own bucket `findings`, written at its
|
|
// raise and deleted at its clear, which the condition keeper raises and clears. Until the operator's take-over the router
|
|
// hears it straight from the assigner, as before; from the take-over the keeper's condition is the one said, so the
|
|
// assigner no longer says it straight (never both).
|
|
|
|
// FindingsBucket is the assigner's findings.
|
|
var FindingsBucket = health.FindingsBucket(delivery.AssignerModule)
|
|
|
|
// FindingOf is a said condition as a finding: its key `<scope>.<id>.<kind>`, from the condition's own key
|
|
// `mesh-delivery-assigner.<kind>.<id…>` (the subject's id when the key names none), its words unchanged. raised is false for
|
|
// a clear, whose key is to be deleted; value is nil then.
|
|
func FindingOf(c asks.Condition, at time.Time) (key string, value []byte, raised bool, err error) {
|
|
rest, ok := strings.CutPrefix(c.Key, delivery.AssignerModule+".")
|
|
if !ok || rest == "" {
|
|
return "", nil, false, fmt.Errorf("the condition %q is not the assigner's", c.Key)
|
|
}
|
|
kind, id, _ := strings.Cut(rest, ".")
|
|
if id == "" {
|
|
id = c.Subject.ID
|
|
}
|
|
scope := c.Subject.Scope
|
|
if scope == "" {
|
|
scope = health.ScopeModule
|
|
}
|
|
key = health.FindingKey(scope, id, kind)
|
|
if c.State == asks.StateCleared {
|
|
return key, nil, false, nil
|
|
}
|
|
if c.State != asks.StateRaised {
|
|
return "", nil, false, fmt.Errorf("the condition %s is neither raised nor cleared", c.Key)
|
|
}
|
|
summary := c.Summary
|
|
if summary == "" {
|
|
summary = c.Headline
|
|
}
|
|
var actions []health.Action
|
|
for _, a := range c.Actions {
|
|
actions = append(actions, health.Action{Label: a.Label, Verb: a.Verb, Machine: a.Machine, Arguments: a.Arguments, Level: string(a.Level)})
|
|
}
|
|
value, err = health.EncodeFinding(health.Finding{Key: key, Kind: kind, Subject: health.Subject{Scope: scope, ID: id, Machine: c.Subject.Machine},
|
|
Severity: health.Severity(c.Severity), Summary: summary, Headline: c.Headline, Explanation: c.Explanation, Resolved: c.Resolved,
|
|
Needs: c.Needs, Actions: actions, Evidence: []health.Evidence{{At: at.UTC(), Said: summary}}, Source: "assigner." + kind,
|
|
Observer: delivery.AssignerModule, At: at.UTC()})
|
|
return key, value, true, err
|
|
}
|
|
|
|
// Findings is where the assigner's findings go: the bucket's put and delete.
|
|
type Findings struct {
|
|
Put func(key string, value []byte) error
|
|
Delete func(key string) error
|
|
Keys func() ([]string, error)
|
|
Now func() time.Time
|
|
}
|
|
|
|
// Say is say with each condition written as a finding (or its finding deleted), which the condition keeper carries, the
|
|
// one source since the controller's end (novox/hq ADR 0590 §3); said straight to the router only where there is no
|
|
// findings bucket at all. A finding that cannot be written is the error.
|
|
func (f Findings) Say(say func(asks.Condition) error) func(asks.Condition) error {
|
|
return func(c asks.Condition) error {
|
|
var errs []error
|
|
if f.Put != nil {
|
|
now := time.Now
|
|
if f.Now != nil {
|
|
now = f.Now
|
|
}
|
|
key, value, raised, err := FindingOf(c, now())
|
|
switch {
|
|
case err != nil:
|
|
errs = append(errs, err)
|
|
case raised:
|
|
if err := f.Put(key, value); err != nil {
|
|
errs = append(errs, fmt.Errorf("the finding %s cannot be written: %w", key, err))
|
|
}
|
|
default:
|
|
if err := f.Delete(key); err != nil {
|
|
errs = append(errs, fmt.Errorf("the finding %s cannot be deleted: %w", key, err))
|
|
}
|
|
}
|
|
}
|
|
if f.Put == nil {
|
|
if say != nil {
|
|
if err := say(c); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
}
|
|
return errors.Join(errs...)
|
|
}
|
|
}
|