From fc83f204f4983986706102c444f3d65ff7279cdf Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 22:15:42 +0100 Subject: [PATCH] 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 --- .../cmd/distribution-tools/main.go | 21 +++++++------- .../distribution/internal/holder/findings.go | 28 +++++++++++++++++-- .../gitea/cmd/package-registry/findings.go | 28 +++++++++++++++++-- modules/gitea/cmd/package-registry/main.go | 19 ++++++------- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/modules/distribution/cmd/distribution-tools/main.go b/modules/distribution/cmd/distribution-tools/main.go index 296daf919..af47a63f0 100644 --- a/modules/distribution/cmd/distribution-tools/main.go +++ b/modules/distribution/cmd/distribution-tools/main.go @@ -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) } diff --git a/modules/distribution/internal/holder/findings.go b/modules/distribution/internal/holder/findings.go index e14a07b84..ffdb8f99f 100644 --- a/modules/distribution/internal/holder/findings.go +++ b/modules/distribution/internal/holder/findings.go @@ -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) } } diff --git a/modules/gitea/cmd/package-registry/findings.go b/modules/gitea/cmd/package-registry/findings.go index ebbce5955..330f545c5 100644 --- a/modules/gitea/cmd/package-registry/findings.go +++ b/modules/gitea/cmd/package-registry/findings.go @@ -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) } } diff --git a/modules/gitea/cmd/package-registry/main.go b/modules/gitea/cmd/package-registry/main.go index 5a66e7a22..0314439d5 100644 --- a/modules/gitea/cmd/package-registry/main.go +++ b/modules/gitea/cmd/package-registry/main.go @@ -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) }