W16.6: the tool runner reads which modules exist and their manifests from the catalog half through meshcatalogclient's branch follower on its own consumer (milestone 47/G)
firstview adopts the shared branch follower over a wiring of its own consumer (views held with the assigner's, findings through its held writer, events routed to each adopted view); the console's Source gives Modules from the catalog entries, the manifest an entry carries whole; the indexer's modules and manifests first views and their consumes are removed, the registrations and the gate's source-moved are consumed. Builds: design 47 G §196, §594, §614; lead answers A-5, A-27, A-103 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -19,7 +19,7 @@ package console
|
||||
import (
|
||||
assigner "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
meshcatalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
|
||||
"context"
|
||||
@@ -95,8 +95,7 @@ func discovery() []map[string]any {
|
||||
"bus, and when it was last heard — and every runtime or machine expected that was not heard. Read-only; " +
|
||||
"for when an address is said to be missing or on another machine."},
|
||||
{"name": verbFirstViews, "inputSchema": obj(map[string]any{}),
|
||||
"description": "The tool runner's first views of the four states it reads of the indexer and the assigner (the " +
|
||||
"assigner's assignments and seat-holders, the indexer's manifests and modules): for each, the owner, the tool " +
|
||||
"description": "The tool runner's first views: the assigner's assignments and seat-holders, and the catalog half's registrations, the gate's source and the view of each watched branch (which modules exist, each with its manifest): for each, the owner, the tool " +
|
||||
"asked, the revision held and when it was made, how many keys, when it was asked and answered, how often " +
|
||||
"it was asked and how often by hand, whether its condition is raised, and why it was last asked. Read-only."},
|
||||
{"name": verbFirstAgain, "inputSchema": obj(map[string]any{
|
||||
@@ -359,14 +358,14 @@ func indexOn(conn *bus.Conn, src Source, held announce.Known, before func(announ
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// Source is where the console reads the four states of the indexer and the assigner it indexes the mesh by: the assigner's
|
||||
// assignments and seat-holders, the indexer's modules and manifests, by `<owner>.<state>` (firstview.AssignmentsRead and the
|
||||
// rest). The tool runner's is its first views (firstview.Views), never the owners' state (novox/hq ADR 0615 §1).
|
||||
// Source is where the console reads what it indexes the mesh by: the assigner's assignments and seat-holders, by
|
||||
// `<owner>.<state>` (firstview.AssignmentsRead and SeatHoldersRead), and the modules of the catalog half with their entries.
|
||||
// The tool runner's is its first views (firstview.Views), never the owners' state (novox/hq ADR 0615 §1; design 47 G §196, §594).
|
||||
type Source interface {
|
||||
// State is every current value of a state by key; an error says why it cannot be told yet.
|
||||
State(name string) (map[string][]byte, error)
|
||||
// Entry is one value of a state, nil when it holds none.
|
||||
Entry(name, key string) ([]byte, error)
|
||||
// Modules is every module the catalog half holds, by name, with its entry; an error says why it cannot be told yet.
|
||||
Modules() (map[string]meshcatalog.CatalogEntry, error)
|
||||
}
|
||||
|
||||
// seatHoldersOn reads the state seat-holders whole (novox/hq ADR 0571 clause 4), as firstview.SeatHoldersRead.
|
||||
@@ -374,22 +373,12 @@ func seatHoldersOn(src Source) (map[string][]byte, error) {
|
||||
return src.State(firstview.SeatHoldersRead)
|
||||
}
|
||||
|
||||
// The indexer's states the console reads (novox/hq ADR 0571 clauses 4 and 5): every module, and each manifest by its
|
||||
// digest, by the names a reader's manifest declares.
|
||||
var (
|
||||
modulesRead = firstview.ModulesRead
|
||||
manifestsRead = firstview.ManifestsRead
|
||||
)
|
||||
|
||||
// manifestFacts is what the console takes of a manifest: the tools it declares and what each replaces. A manifest's
|
||||
// digest never names another, so what is read once is kept.
|
||||
// manifestFacts is what the console takes of a manifest: the tools it declares and what each replaces.
|
||||
type manifestFacts struct {
|
||||
Tools []string `json:"tools"`
|
||||
Replaces map[string][]string `json:"replaces"`
|
||||
}
|
||||
|
||||
var manifestsKept sync.Map // digest → manifestFacts
|
||||
|
||||
// recordsOn is every module the indexer holds, with where the assigner seat places it and whether it declares tools,
|
||||
// and every machine the assignments name (moduleNames, assigner.MachineNames). What cannot be read is said, one line
|
||||
// per state, for the listing's not answering.
|
||||
@@ -411,18 +400,19 @@ func recordsOn(src Source) (recorded []recordedModule, machines []string, why []
|
||||
on[m.Module] = append(on[m.Module], node)
|
||||
}
|
||||
}
|
||||
modules, err := src.State(modulesRead)
|
||||
entries, err := src.Modules()
|
||||
if err != nil {
|
||||
return nil, machines, append(why, modulesRead+" (which modules there are could not be read: "+err.Error()+")")
|
||||
return nil, machines, append(why, "the catalog half's modules (which modules there are could not be read: "+err.Error()+")")
|
||||
}
|
||||
names, err := moduleNames(modules)
|
||||
if err != nil {
|
||||
return nil, machines, append(why, modulesRead+" ("+err.Error()+")")
|
||||
names := make([]string, 0, len(entries))
|
||||
for name := range entries {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
r := recordedModule{Module: name, On: on[name]}
|
||||
if m, err := meshcatalogclient.DecodeModule(modules[name]); err == nil && m.Manifest != "" {
|
||||
facts, err := manifestOf(src, m.Manifest)
|
||||
if m := entries[name].Manifest; len(m) > 0 {
|
||||
facts, err := manifestOf(m)
|
||||
if err != nil {
|
||||
why = append(why, name+" (its manifest could not be read, so whether it answers tools is not known: "+err.Error()+")")
|
||||
} else {
|
||||
@@ -434,42 +424,16 @@ func recordsOn(src Source) (recorded []recordedModule, machines []string, why []
|
||||
return recorded, machines, why
|
||||
}
|
||||
|
||||
// moduleNames is every module the indexer holds, sorted, from its state `modules` as a reader holds its keys (key to
|
||||
// value; a deleted key absent). A key that is not a module's is refused, naming it.
|
||||
func moduleNames(keys map[string][]byte) ([]string, error) {
|
||||
out := make([]string, 0, len(keys))
|
||||
for k := range keys {
|
||||
module, err := meshcatalogclient.ParseModulesKey(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, module)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// manifestOf is what the console takes of the manifest of a digest, read from the indexer's manifests once.
|
||||
func manifestOf(src Source, digest string) (manifestFacts, error) {
|
||||
if kept, ok := manifestsKept.Load(digest); ok {
|
||||
return kept.(manifestFacts), nil
|
||||
}
|
||||
entry, err := src.Entry(manifestsRead, digest)
|
||||
if err != nil {
|
||||
return manifestFacts{}, err
|
||||
}
|
||||
if entry == nil {
|
||||
return manifestFacts{}, fmt.Errorf("the indexer holds no manifest %s", digest)
|
||||
}
|
||||
m, err := meshcatalogclient.DecodeManifest(entry)
|
||||
// manifestOf is what the console takes of the manifest an entry of the catalog half carries whole (design 47 G §443).
|
||||
func manifestOf(manifest map[string]any) (manifestFacts, error) {
|
||||
raw, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return manifestFacts{}, err
|
||||
}
|
||||
var facts manifestFacts
|
||||
if err := json.Unmarshal([]byte(m.Manifest), &facts); err != nil {
|
||||
if err := json.Unmarshal(raw, &facts); err != nil {
|
||||
return manifestFacts{}, err
|
||||
}
|
||||
manifestsKept.Store(digest, facts)
|
||||
return facts, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ func (s *Surface) firstViewsAnswer(ctx context.Context, name string, args map[st
|
||||
}
|
||||
// A hand act, recorded through the seat mesh-hand-acts (novox/hq ADR 0615 §6, ADR 0565 §10) before it is done, and in the
|
||||
// runner's log. One that cannot be recorded is said, and the ask is not made.
|
||||
log.Printf("[mesh-tools] hand act: %s asked the four first views again: %s", s.who, why)
|
||||
if err := s.recordAct(map[string]any{"what": "asked the tool runner's four first views again", "why": why,
|
||||
log.Printf("[mesh-tools] hand act: %s asked the first views again: %s", s.who, why)
|
||||
if err := s.recordAct(map[string]any{"what": "asked the tool runner's first views again", "why": why,
|
||||
"cause": "first-view-again", "verb": verbFirstAgain}); err != nil {
|
||||
return failure("the hand act was not recorded through mesh-hand-acts, so the first views were not asked again: " + err.Error())
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ func TestTheManifestNamesTheFirstFamiliesOneByOne(t *testing.T) {
|
||||
t.Errorf("a family named by a wildcard: %s", w)
|
||||
}
|
||||
}
|
||||
// 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 the two events of the seat that serves the seat list (ADR 0636 §4), and nothing else.
|
||||
// Design 47 G §196, §387: the catalog half by `catalog-revision` (never the indexer's `views.modules`), and the keeper's
|
||||
// `findings-asked`; the finding stream below. `views.modules` goes with the first view that follows it (T-2).
|
||||
wantConsumes := []string{"mesh-catalog.catalog-revision", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed",
|
||||
"mesh-conditions.findings-asked", "mesh-delivery-assigner.views.assignments", "mesh-delivery-assigner.views.seat-holders",
|
||||
"mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules"}
|
||||
// A wait is no consumer (ADR 0367 §2): what it consumes is the assigner's two views families (ADR 0615 §5, issue 1966
|
||||
// TLS-5), the events of the catalog half's branch follower (design 47 G §196, §614: `catalog-revision`,
|
||||
// `registration-changed`, and the gate's `source-moved`; never the indexer's `views.modules` or `views.manifests`), the two
|
||||
// events of the seat that serves the seat list (ADR 0636 §4) and the keeper's `findings-asked`, and nothing else.
|
||||
wantConsumes := []string{"mesh-catalog.catalog-revision", "mesh-catalog.registration-changed", "mesh-catalog.seat-definition-changed",
|
||||
"mesh-catalog.seat-definition-removed", "mesh-conditions.findings-asked", "mesh-delivery-assigner.views.assignments",
|
||||
"mesh-delivery-assigner.views.seat-holders", "mesh-delivery.source-moved"}
|
||||
if !reflect.DeepEqual(m.Consumes, wantConsumes) {
|
||||
t.Errorf("the tool runner consumes %v, want %v", m.Consumes, wantConsumes)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
|
||||
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
||||
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
|
||||
)
|
||||
|
||||
// busSource is a Source over a bus connection's own states, which a test's fixture mesh fills: the console's tests stand a
|
||||
@@ -37,3 +41,20 @@ func (b busSource) Entry(name, key string) ([]byte, error) {
|
||||
}
|
||||
return entry.Value, nil
|
||||
}
|
||||
|
||||
// Modules reads the test bed's entries of the catalog half (meshtest.ModulesRead), as the tool runner's first views hold them.
|
||||
func (b busSource) Modules() (map[string]meshcatalog.CatalogEntry, error) {
|
||||
raw, err := b.State(mt.ModulesRead)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[string]meshcatalog.CatalogEntry{}
|
||||
for module, value := range raw {
|
||||
var e meshcatalog.CatalogEntry
|
||||
if err := json.Unmarshal(value, &e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[module] = e
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user