G findings: the held writers of the stdio bundles are made once the runtime has its channel

The held writer reads the keys of its state when it is made, so it is made after Serve answers state calls; the keeper says a finding is not said before it exists.

Builds: design 47 G A-32, A-29, A-37
This commit is contained in:
2026-11-11 22:15:42 +01:00
parent 9f4d4eecba
commit fc83f204f4
4 changed files with 69 additions and 27 deletions
@@ -505,23 +505,22 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
var findings = &holder.Findings{Logf: logf}
func main() {
// The tools bundle's findings go through the module's writer (design 47 G §689, A-16); it owns the transfers' kinds, the
// program's writer the driver's.
w, err := views.NewStdioHeldWriter(views.WriterConfig[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent]{
Asked: meshconditions.SubjectFindingsAsked, Module: holder.Self, State: stdio.State("findings"), Kinds: holder.TransferFindingKinds, Logf: logf})
if err != nil {
logf("%v", err)
os.Exit(1)
}
findings.Writer = w
// the runtime answers the writer's subscription only once Serve has its channel
// The tools bundle's findings go through the module's held writer (design 47 G §689, A-16, A-29, A-32); it owns the transfers'
// kinds, the program's writer the driver's. The runtime answers its state and subscription only once Serve has its channel,
// so it is made then.
go func() {
<-stdio.Served()
// the keys of the older form end in no kind the writer owns, so it does not clear them (A-31)
if err := holder.DeleteOldFindings(stdio.State("findings"), holder.Self, holder.TransferFindingKinds); err != nil {
logf("the old findings are not deleted: %v", err)
}
if err := w.Start(context.Background()); err != nil {
w, err := views.NewStdioHeldWriter(views.WriterConfig[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent]{
Asked: meshconditions.SubjectFindingsAsked, Module: holder.Self, State: stdio.State("findings"), Kinds: holder.TransferFindingKinds, Logf: logf})
if err == nil {
findings.Use(w)
err = w.Start(context.Background())
}
if err != nil {
logf("%v", err)
os.Exit(1)
}
@@ -3,6 +3,7 @@ package holder
import (
"context"
"strings"
"sync"
"time"
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
@@ -26,9 +27,25 @@ type FindingWriter interface {
// Findings keeps a bundle's open findings and says them through the module's writer (design 47 G §689, A-14): its `Say` is
// the Finding callback of the Driver and the Holder; the writer is the held writer (views.HeldWriter), which holds what is open (A-29, A-32).
type Findings struct {
Writer FindingWriter
Writer FindingWriter // set by Use once the runtime has its channel (the held writer reads its state at its start)
Logf func(string, ...any)
Now func() time.Time
mu sync.Mutex
}
// Use sets the writer, once the runtime answers a bundle's state calls.
func (k *Findings) Use(w FindingWriter) {
k.mu.Lock()
k.Writer = w
k.mu.Unlock()
}
// writer is the writer set, nil before Use.
func (k *Findings) writer() FindingWriter {
k.mu.Lock()
defer k.mu.Unlock()
return k.Writer
}
// Say raises (raised) or clears the finding of a kind about a subject. Its id is the seat's and the subject's, each escaped
@@ -36,8 +53,13 @@ type Findings struct {
func (k *Findings) Say(kind, subject, words string, raised bool) {
id := keyparts.EscapeName(Self) + "." + keyparts.EscapeName(subject)
key := views.FindingKey("seat", id, kind)
w := k.writer()
if w == nil {
k.Logf("the finding %s is not said: the writer is not made yet", key)
return
}
if !raised {
if err := k.Writer.Clear(context.Background(), "seat", id, kind, "it no longer holds"); err != nil {
if err := w.Clear(context.Background(), "seat", id, kind, "it no longer holds"); err != nil {
k.Logf("the finding %s could not be cleared: %v", key, err)
}
return
@@ -49,7 +71,7 @@ func (k *Findings) Say(kind, subject, words string, raised bool) {
f := meshconditions.Finding{Scope: "seat", ID: id, Kind: kind, Subject: meshconditions.Subject{Scope: "seat", ID: id},
Severity: meshconditions.FindingSeverityWarning, Summary: words, Headline: words, Explanation: words,
Source: kind, Observer: Self, At: at.UTC()}
if err := k.Writer.Write(context.Background(), f); err != nil {
if err := w.Write(context.Background(), f); err != nil {
k.Logf("the finding %s could not be written: %v", key, err)
}
}
+25 -3
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"strings"
"sync"
"time"
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
@@ -25,9 +26,25 @@ type findingWriter interface {
// Findings keeps a bundle's open findings and says them through the module's writer (design 47 G §689, A-14): its `Say` is
// the Finding callback of the Driver and the Holder; the writer is the held writer (views.HeldWriter), which holds what is open (A-29, A-32).
type Findings struct {
Writer findingWriter
Writer findingWriter // set by Use once the runtime has its channel (the held writer reads its state at its start)
Logf func(string, ...any)
Now func() time.Time
mu sync.Mutex
}
// Use sets the writer, once the runtime answers a bundle's state calls.
func (k *Findings) Use(w findingWriter) {
k.mu.Lock()
k.Writer = w
k.mu.Unlock()
}
// writer is the writer set, nil before Use.
func (k *Findings) writer() findingWriter {
k.mu.Lock()
defer k.mu.Unlock()
return k.Writer
}
// Say raises (raised) or clears the finding of a kind about a subject. Its id is the seat's and the subject's, each escaped
@@ -35,8 +52,13 @@ type Findings struct {
func (k *Findings) Say(kind, subject, words string, raised bool) {
id := keyparts.EscapeName(packageseats.Self) + "." + keyparts.EscapeName(subject)
key := views.FindingKey("seat", id, kind)
w := k.writer()
if w == nil {
k.Logf("the finding %s is not said: the writer is not made yet", key)
return
}
if !raised {
if err := k.Writer.Clear(context.Background(), "seat", id, kind, "it no longer holds"); err != nil {
if err := w.Clear(context.Background(), "seat", id, kind, "it no longer holds"); err != nil {
k.Logf("the finding %s could not be cleared: %v", key, err)
}
return
@@ -48,7 +70,7 @@ func (k *Findings) Say(kind, subject, words string, raised bool) {
f := meshconditions.Finding{Scope: "seat", ID: id, Kind: kind, Subject: meshconditions.Subject{Scope: "seat", ID: id},
Severity: meshconditions.FindingSeverityWarning, Summary: words, Headline: words, Explanation: words,
Source: kind, Observer: packageseats.Self, At: at.UTC()}
if err := k.Writer.Write(context.Background(), f); err != nil {
if err := w.Write(context.Background(), f); err != nil {
k.Logf("the finding %s could not be written: %v", key, err)
}
}
+9 -10
View File
@@ -29,22 +29,21 @@ var ToolNames = []string{"go_packages", "go_retention", "go_incoming", "npm_pack
var findings = &Findings{Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, f+"\n", a...) }}
func main() {
// The bundle's findings go through the module's writer (design 47 G A-16).
w, err := views.NewStdioHeldWriter(views.WriterConfig[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent]{
Asked: meshconditions.SubjectFindingsAsked, Module: packageseats.Self, State: stdio.State("findings"), Kinds: findingKinds})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
findings.Writer = w
// the runtime answers the writer's subscription only once Serve has its channel
// The bundle's findings go through the module's held writer (design 47 G A-16, A-29, A-32). The runtime answers the writer's
// state and subscription only once Serve has its channel, so it is made then.
go func() {
<-stdio.Served()
// the keys of the older form end in no kind the writer owns, so it does not clear them (A-31)
if err := DeleteOldFindings(stdio.State("findings"), packageseats.Self, findingKinds); err != nil {
fmt.Fprintln(os.Stderr, "the old findings are not deleted:", err)
}
if err := w.Start(context.Background()); err != nil {
w, err := views.NewStdioHeldWriter(views.WriterConfig[meshconditions.Finding, meshconditions.FindingCleared, meshconditions.FindingsStated, meshconditions.FindingsAskedEvent]{
Asked: meshconditions.SubjectFindingsAsked, Module: packageseats.Self, State: stdio.State("findings"), Kinds: findingKinds})
if err == nil {
findings.Use(w)
err = w.Start(context.Background())
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}