Files
mesh-sdk/go/views/findings.go
T
jschoubben 5fa64be894 Revert the revert of G's mesh-sdk merge (#191): G's sdk stands again over reader release 3
Main reverted G's half merge (#189, reverted by 8afc3eb in #191) and then took the reader release's S1-S5, which
copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the
release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's
empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's head f30dee54 only by those and
the release's two tests (objects cap option, seat states and stream kinds).

Builds: design 47 G (the milestone's sdk part, as at f30dee54)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 12:49:56 +01:00

123 lines
6.0 KiB
Go

package views
import (
"cmp"
"context"
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// 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"
// ViewWaitsKind is a part's finding of a view it has waited for past WaitsBound (design 47 *A part with no view says it
// waits*).
ViewWaitsKind = "view-waits"
// EntryNotFoundKind is a part's finding of an entry by fingerprint the seat mesh-catalog's `entry` answered it does not hold
// (design 47 G §614, A-15, A-103): scope the part, id the fingerprint.
EntryNotFoundKind = "entry-not-found"
)
// viewFinding is what a views part says is wrong with a state it owns or follows, in plain fields: kind and id name it, summary
// and headline say it. It is laid out in the wire form of the contract's finding by mesh-sdk (body, design 47 G §687, A-22) and
// written through the part's generic writer; there is no other form of it.
type viewFinding struct {
kind string
id string
summary string
headline string
at time.Time
}
// WireWriter is what a views part's findings use of the part's finding writer, whatever the contract's body types: the
// generic Writer[F, C, S, A] is one (design 47 G §639, §687, A-22).
type WireWriter interface {
WriteWire(ctx context.Context, finding map[string]any) error
Clear(ctx context.Context, scope, id, kind, why string) error
ModuleNode() (module, node string)
}
// FindingScope is the scope of a views reader's findings: they are findings of the part's module (design 47 G §687).
const FindingScope = "module"
// body lays the finding out on its wire form (design 47 G §687): `kind` the finding's, `scope` module, `id` the finding's id
// escaped as a key's name is, `severity` warning but urgent for view-too-large, `source` views, `subject` the part's module on
// its node, `summary` and `headline` the finding's texts.
func (f viewFinding) body(module, node string) map[string]any {
sev := "warning"
if f.kind == TooLargeKind {
sev = "urgent"
}
subject := map[string]any{"scope": FindingScope, "id": module}
if node != "" {
subject["machine"] = node
}
b := map[string]any{"kind": f.kind, "scope": FindingScope, "id": keys.EscapeName(f.id), "severity": sev, "source": "views",
"subject": subject, "summary": f.summary}
if f.headline != "" {
b["headline"] = f.headline
}
if !f.at.IsZero() {
b["at"] = f.at
}
return b
}
// put writes the finding through w; drop clears it.
func (f viewFinding) put(w WireWriter) error {
module, node := w.ModuleNode()
return w.WriteWire(context.Background(), f.body(module, node))
}
func (f viewFinding) drop(w WireWriter) error {
return w.Clear(context.Background(), FindingScope, keys.EscapeName(f.id), f.kind, "the part no longer observes it")
}
func unsaidFinding(owner, state, key string, revision uint64, since time.Time, cause string, at time.Time) viewFinding {
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 viewFinding{kind: UnsaidKind, id: owner + "." + state, summary: summary, headline: fmt.Sprintf("%s cannot say a change of its %s", owner, state), at: at.UTC()}
}
func tooLargeFinding(owner, state, key string, size int, at time.Time) viewFinding {
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 viewFinding{kind: TooLargeKind, id: owner + "." + state + "." + key, summary: summary, headline: fmt.Sprintf("A value of %s is too large for its views event", owner), at: at.UTC()}
}
func unansweredFinding(reader string, s Status, at time.Time) viewFinding {
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 viewFinding{kind: UnansweredKind, id: reader + "." + s.Owner + "." + s.State, summary: summary, headline: fmt.Sprintf("%s waits for %s", reader, s.Owner), at: at.UTC()}
}
func viewWaitsFinding(part string, s Status, at time.Time) viewFinding {
summary := fmt.Sprintf("%s waits for the view %s.%s at revision %s since %s and acts on nothing new until it holds it; it holds %s",
part, s.Owner, s.State, s.WaitsFor, s.WaitingSince.UTC().Format(time.RFC3339), cmp.Or(s.View, "none"))
if s.WaitsFor == HolderWait {
summary = seatWaitsSummary(part, s) // a seat's view with no holder yet (design 47 G §717)
}
return viewFinding{kind: ViewWaitsKind, id: part + "." + s.Owner + "." + s.State, summary: summary, headline: fmt.Sprintf("%s waits for a view of %s", part, s.Owner), at: at.UTC()}
}
// WriteEntryNotFound writes the part's finding that the catalog holds no entry for fingerprint, asked since `since`
// (design 47 G A-103); ClearEntryNotFound clears it, when nothing the part holds names the fingerprint or a later ask answers.
func WriteEntryNotFound(w WireWriter, fingerprint string, since, at time.Time) error {
module, _ := w.ModuleNode()
return viewFinding{kind: EntryNotFoundKind, id: fingerprint,
summary: fmt.Sprintf("%s holds a build of fingerprint %s that the catalog holds no entry for (asked since %s): what it needs of the entry stands still",
module, fingerprint, since.UTC().Format(time.RFC3339)),
headline: fmt.Sprintf("%s cannot find the catalog entry of a build", module), at: at.UTC()}.put(w)
}
// ClearEntryNotFound clears the finding WriteEntryNotFound wrote for fingerprint.
func ClearEntryNotFound(w WireWriter, fingerprint string) error {
return viewFinding{kind: EntryNotFoundKind, id: fingerprint}.drop(w)
}