Merge b6/registry into the registry milestone's join (issue 1966)

This commit is contained in:
2026-11-05 14:39:37 +01:00
10 changed files with 95 additions and 6 deletions
+2
View File
@@ -50,6 +50,8 @@ func main() {
until := time.Now().Add(startWait)
cred := credential(until)
conn := connectPatiently(cred, until)
// And the seat list it describes the seats by, read through the seat mesh-catalog (novox/hq ADR 0636 §4).
_ = startSeatList(conn)
served, err := runtime.ServedModulesFrom(os.Getenv(runtime.ToolModules), cred.Module)
if err != nil {
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatdef"
"git.novox.be/novox/mesh-sdk/go/seatlist"
"github.com/novox/mesh-tools/node-tools/internal/bus"
)
// The tool runner reads the seat list as every part does (novox/hq ADR 0635 §1, ADR 0636): through the seat mesh-catalog,
// from the bootstrapped definitions until it has been read, and following the seat's events. What it holds is given to
// catalogue at every change; the console describes the seats from it, so the descriptions are the mesh's own and not a
// list compiled into the runner.
// busOfTheRunner is the reader's bus over the runner's connection: a verb of the seat asked on its subject, an event heard
// on the seat's event subject.
type busOfTheRunner struct{ conn *bus.Conn }
func (b busOfTheRunner) Ask(key string, body any) (json.RawMessage, error) {
seat, verb, _ := strings.Cut(strings.TrimPrefix(key, "seat:"), ".")
answered, err := b.conn.Ask(key, body, "mesh.seat."+seat+".tool."+verb)
if err != nil {
return nil, err
}
return answered.Result, nil
}
func (b busOfTheRunner) Subscribe(pattern string, handle func(string, json.RawMessage) error) error {
seat, event, _ := strings.Cut(pattern, ".")
_, err := b.conn.Raw(seatdef.EventSubject(seat, event), func(_ string, data []byte) []byte {
_ = handle(pattern, data)
return nil // an event is answered by nobody
})
return err
}
// startSeatList starts the runner's reader and gives catalogue its bootstrapped seats at once.
func startSeatList(conn *bus.Conn) *seatlist.Reader {
reader, err := seatlist.Start(seatlist.Config{Bus: busOfTheRunner{conn}, Bootstrap: seatlist.Bootstrapped(),
OnChange: func(seats []catalogue.Seat) { catalogue.UseSeats(seats) }})
if err != nil {
log.Printf("node-tools: %v", fmt.Errorf("the tool runner cannot follow the seat list: %w", err))
}
if reader != nil {
catalogue.UseSeats(reader.Seats())
}
return reader
}
+2
View File
@@ -3,6 +3,8 @@ module github.com/novox/mesh-tools/node-tools
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-20261105000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-20261105000000-000000000000
github.com/nats-io/nats.go v1.54.0
golang.org/x/sys v0.48.0
)
+1 -1
View File
@@ -321,7 +321,7 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ
// What should have answered: every assignment of a module that declares tools. Silence is named;
// a module with no tools is never a name here.
x.Replaces = replacesOf(catalogue.DefaultSeats(), recorded)
x.Replaces = replacesOf(catalogue.Seats(), recorded)
for _, m := range recorded {
if !m.Tools {
continue
@@ -36,8 +36,8 @@ func TestTheManifestNamesTheFirstFamiliesOneByOne(t *testing.T) {
}
}
// A wait is no consumer (ADR 0367 §2): what it consumes is the four views families of its first views (ADR 0615 §5, issue
// 1966 TLS-5), and nothing else.
wantConsumes := []string{"mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.seat-holders",
// 1966 TLS-5) and the two events of the seat that serves the seat list (ADR 0636 §4), and nothing else.
wantConsumes := []string{"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.seat-holders",
"mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules"}
if !reflect.DeepEqual(m.Consumes, wantConsumes) {
t.Errorf("the tool runner consumes %v, want %v", m.Consumes, wantConsumes)
+1 -1
View File
@@ -75,7 +75,7 @@ func (s *Surface) seatsAnswer() map[string]any {
placed, err := seatHoldersOn(s.src)
x := &index{Placed: placed}
seats := []map[string]any{}
for _, st := range catalogue.DefaultSeats() {
for _, st := range catalogue.Seats() {
holders := x.placedHolders(st.Name, "")
if holders == nil {
holders = []delivery.SeatHolder{}
+1 -1
View File
@@ -46,7 +46,7 @@ func meshToSearch(t *testing.T) *index {
Description: does, Subject: "mesh.mod.docker.tool." + tool + ".anchor"})
}
x, _, _ := indexOfEndpoints(endpoints)
x.Replaces = replacesOf(catalogue.DefaultSeats(), []recordedModule{{Module: "docker", On: []string{"anchor"}, Tools: true,
x.Replaces = replacesOf(catalogue.Seats(), []recordedModule{{Module: "docker", On: []string{"anchor"}, Tools: true,
Replaces: map[string][]string{"docker_list": {"docker ps"}, "docker_logs": {"docker logs"}, "docker_restart": {"docker restart"}}}})
return x
}
@@ -0,0 +1,10 @@
package console
import (
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatlist/seatlisttest"
)
// The seats the tests describe are given to catalogue as the tool runner gives the ones it read from the bus
// (novox/hq ADR 0635 §1); catalogue compiles none.
func init() { catalogue.UseSeats(catalogue.SeatsFromDefinitions(seatlisttest.Definitions())) }
+21 -1
View File
@@ -11,6 +11,8 @@ import (
"context"
"encoding/json"
"fmt"
conditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
"sort"
"sync"
"time"
@@ -135,7 +137,7 @@ func (v *Views) hear(env bus.Envelope) error {
// finding writes or removes the runner's finding of a first view unanswered past its bound, in its own state.
func (v *Views) finding(st views.Status, raised bool) {
key, value, err := views.UnansweredFinding(v.deps.Module, st, time.Now())
key, value, err := conditionOf(views.UnansweredFinding(v.deps.Module, st, time.Now()))
if err != nil {
v.logf("[mesh-tools] the finding of the first view %s is not made: %v", st.Tool, err)
return
@@ -198,3 +200,21 @@ func (v *Views) Again(ctx context.Context, why string) []error {
}
return errs
}
// conditionOf is a finding in the layout of the seat mesh-conditions' state findings (its contract, ADR 0634 §1), about a
// module, a warning.
func conditionOf(f views.Finding) (string, []byte, error) {
key := conditions.Key(scopeModule, f.ID, f.Kind)
v := condseat.FindingsByKeyValue{V: 1, Key: key, Kind: f.Kind, Observer: f.Observer, Severity: conditions.Warning,
Subject: condseat.Subject{Scope: scopeModule, ID: f.ID}, Summary: f.Summary, Headline: f.Headline,
Explanation: f.Explanation, Source: f.Observer + "." + f.Kind, At: f.At.UTC().Format(time.RFC3339),
Evidence: []condseat.Evidence{{At: f.At.UTC().Format(time.RFC3339), Said: f.Summary}}}
if err := conditions.CheckFinding(v); err != nil {
return "", nil, err
}
raw, err := json.Marshal(v)
return key, raw, err
}
// scopeModule is the scope of a finding about a module.
const scopeModule = "module"
+2
View File
@@ -71,6 +71,8 @@
"findings"
],
"consumes": [
"mesh-catalog.seat-definition-changed",
"mesh-catalog.seat-definition-removed",
"mesh-delivery-assigner.views.placement",
"mesh-delivery-assigner.views.seat-holders",
"mesh-delivery-indexer.views.manifests",