Main reverted G's half merge (#189, reverted by8afc3ebin #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 headf30dee54only 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 atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
123 lines
6.0 KiB
Go
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)
|
|
}
|