Files
mesh-sdk/go/views/findings.go
T
jschoubben be1ffdfedb R2-G-sdk-contracts 1: a view change not said past its bound is the owner's finding
A change said again and again without the bus taking it left every reader standing still with only a log
line. Past UnsaidAfter (Bound by default) the emitter writes the owner's finding view-unsaid naming the
state, as it does for a value too large, and clears it once the change is said.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:16:54 +01:00

61 lines
3.6 KiB
Go

package views
import (
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/health"
)
// The findings a views part writes in its own state `findings`, which the condition keeper raises and clears
// (novox/hq ADR 0564 §2): an owner's value too large for an event (TooLargeKind), an owner's change not said past its
// bound (UnsaidKind), and a reader's first view unanswered past its bound (UnansweredKind).
const (
TooLargeKind = "view-too-large"
UnsaidKind = "view-unsaid"
UnansweredKind = "first-view-unanswered"
)
// UnsaidFinding is the owner's finding of a change of state it has not said on its views family since since: every
// reader of the state stands still at the change before it until it is said, which clears this.
func UnsaidFinding(owner, state, key string, revision uint64, since time.Time, cause string, at time.Time) (string, []byte, error) {
id := owner + "." + state
summary := fmt.Sprintf("%s has not said the change of %s.%s at %d on its views family since %s (%s): its readers stand still before it",
owner, state, key, revision, since.UTC().Format(time.RFC3339), strings.TrimSpace(cause))
return finding(owner, UnsaidKind, id, summary,
fmt.Sprintf("%s cannot say a change of its %s", owner, state),
"The owner says each change of its state as an event on its own stream, and says a change again until the bus takes it; "+
"its readers hear nothing newer meanwhile. Look at the owner's stream on the bus: is it full, or is the bus away?", at)
}
// TooLargeFinding is the owner's finding of state's key too large to say whole on its views family.
func TooLargeFinding(owner, state, key string, size int, at time.Time) (string, []byte, error) {
id := owner + "." + state + "." + key
summary := fmt.Sprintf("%s's %s.%s holds %d bytes, more than one views event carries (%d): every reader asks its first view again at each change of it", owner, state, key, size, MaxValueBytes)
return finding(owner, TooLargeKind, id, summary,
fmt.Sprintf("A value of %s is too large for its views event", owner),
"Each change of this key is said as a gap, so every reader of the state asks the owner's first view once more. "+
"It works, but costs a first view per change; the value should be made smaller or split.", at)
}
// UnansweredFinding is a reader's finding of its first view of s unanswered past its bound (ADR 0615 §6): it names the
// owner and the tool. A first view answered at revision 0 with nothing in it is said too, with the operator's step.
func UnansweredFinding(reader string, s Status, at time.Time) (string, []byte, error) {
id := reader + "." + s.Owner + "." + s.State
summary := fmt.Sprintf("%s has no first view of %s's %s: %s has not answered since %s (%s)", reader, s.Owner, s.State, s.Tool,
s.Asked.Format(time.RFC3339), strings.TrimSpace(s.Error))
return finding(reader, UnansweredKind, id, summary,
fmt.Sprintf("%s waits for %s", reader, s.Owner),
fmt.Sprintf("%s reads %s's %s only through %s, and keeps asking it; it never reads the state itself. The answer clears this. "+
"Look at %s's runtime of its own: is it running and connected?", reader, s.Owner, s.State, s.Tool, s.Owner), at)
}
func finding(observer, kind, id, summary, headline, explanation string, at time.Time) (string, []byte, error) {
key := health.FindingKey(health.ScopeModule, id, kind)
value, err := health.EncodeFinding(health.Finding{Key: key, Kind: kind, Subject: health.Subject{Scope: health.ScopeModule, ID: id},
Severity: health.Warning, Summary: summary, Headline: headline, Explanation: explanation,
Evidence: []health.Evidence{{At: at.UTC(), Said: summary}}, Source: observer + "." + kind, Observer: observer, At: at.UTC()})
return key, value, err
}