C3d REG2: distribution keeps no caller list; the build modules are the catalog half's claimants of node-build-* seats

The config `callers`, the tool registry_callers and its invokes, the caller-list code and the holder's per-verb
Allowed checks go. The windows' driver counts as build modules the modules claiming a `node-build-*` seat in the
catalog half, followed by the seat mesh-catalog's branch follower on the program's views reader (one shared events
hub); it wakes again when a build module's entry moves. The retention check of remove finds the artifact sweep's
holder through the seat mesh-module-assigner's holders (seatlist.HolderOf), not a name.

Builds: design 47 G §610, §7, §428, A-156, A-82
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:30:54 +01:00
parent 9fe3f41ac0
commit 8d73b5ba1b
17 changed files with 273 additions and 332 deletions
@@ -3,7 +3,6 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
@@ -31,7 +30,7 @@ func TestTheVerbsSpeakTheSeatsContract(t *testing.T) {
reg.VerbVersions: reg.VersionsInput{}, reg.VerbRemove: reg.RemoveInput{}, reg.VerbHold: reg.HoldInput{},
reg.VerbRelease: reg.ReleaseInput{}, reg.VerbMirror: reg.MirrorInput{}, reg.VerbTags: reg.TagsInput{}}
seen := 0
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}, func() (holder.Callers, error) { return holder.DefaultCallers(), nil }) {
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}) {
seat, verb, ok := strings.Cut(tool.Name, ".")
in, isVerb := inputs[verb]
if !ok || !isVerb || (seat != holder.ImageSeat && seat != holder.ArtifactSeat) {
@@ -57,28 +56,6 @@ func TestTheVerbsSpeakTheSeatsContract(t *testing.T) {
t.Errorf("%s's body %s is not the contract's: %v", event, b, err)
}
}
// The caller list is read held: one never read answers no verb, and registry_callers says it as read.
unread := func() (holder.Callers, error) { return nil, errors.New("config refused") }
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}, unread) {
if tool.Name == holder.ImageSeat+"."+holder.VerbVersions {
if _, err := tool.Run(map[string]any{"name": "m/a"}); err == nil || !strings.Contains(err.Error(), "caller list") {
t.Errorf("versions with an unread caller list = %v, want refused naming the caller list", err)
}
}
}
answered := false
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}, func() (holder.Callers, error) { return holder.Callers{holder.VerbPublish: {"mesh-build-go"}}, nil }) {
if tool.Name == holder.CallersTool {
answered = true
got, err := tool.Run(map[string]any{})
if b, _ := json.Marshal(got); err != nil || string(b) != `{"callers":{"publish":["mesh-build-go"]}}` {
t.Errorf("%s = %s, %v", holder.CallersTool, b, err)
}
}
}
if !answered {
t.Errorf("no tool %s: the program could not read the caller list", holder.CallersTool)
}
if a := publishAnswer(holder.PublishAnswer{Transfer: "t", Missing: []string{"sha256:bb"}}); !reflect.DeepEqual(a.Lacks, []string{"sha256:bb"}) || a.Held {
t.Errorf("publish answers %+v", a)
}
@@ -23,6 +23,7 @@ import (
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-delivery/sdk/parts"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/seatlist"
"git.novox.be/novox/mesh-sdk/go/views"
"distribution/internal/holder"
@@ -232,16 +233,8 @@ func arg[T any](args map[string]any) (T, error) {
func str(args map[string]any, k string) string { s, _ := args[k].(string); return strings.TrimSpace(s) }
// tools are distribution's tools over v; each call that checks a caller reads the caller list held (callers:
// the config value as mesh-config resolves it now), so an operator's change of it holds from the next call on.
func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.Callers, error)) []stdio.Tool {
now := func() (*holder.Verbs, error) {
c, err := callers()
if err != nil {
return nil, fmt.Errorf("the caller list cannot be read, so no verb is answered: %w", err)
}
return v.WithCallers(c), nil
}
// tools are distribution's tools over v.
func tools(v *holder.Verbs, windows kept[holder.Window]) []stdio.Tool {
obj := func(props map[string]any, req ...string) map[string]any {
return map[string]any{"type": "object", "required": req, "properties": props}
}
@@ -252,10 +245,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: seat + "." + holder.VerbPublish, Description: "Open a transfer of a publish (ADR 0611): its name, version, kind, digest and parts; answers the parts the holder lacks.",
Input: obj(map[string]any{"transfer": s("the transfer's id"), "name": s("<module>/<artifact>"), "version": s("the builder's version"), "kind": s("image, bundle, archive or upstream"), "digest": s("sha256:…"), "parts": map[string]any{"type": "array"}}, "transfer", "name", "kind", "digest", "parts"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
in, err := arg[reg.PublishInput](args)
if err != nil {
return nil, err
@@ -269,10 +258,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: seat + "." + holder.VerbPublishPart, Description: "Send one chunk (at most 1 MiB) of one part of an open transfer, from the caller that opened it.",
Input: obj(map[string]any{"transfer": s("its id"), "part": s("the part's digest"), "sequence": map[string]any{"type": "integer"}, "last": map[string]any{"type": "boolean"}, "data": s("base64")}, "transfer", "part", "sequence", "last", "data"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
in, err := arg[reg.PublishPartInput](args)
if err != nil {
return nil, err
@@ -286,10 +271,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
}},
stdio.Tool{Name: seat + "." + holder.VerbVersions, Description: "The versions the seat holds of a name.", Input: obj(map[string]any{"name": s("<module>/<artifact>")}, "name"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
a, err := v.Versions(seat, stdio.CallerOf(args), str(args, "name"))
if err != nil {
return nil, err
@@ -299,10 +280,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: seat + "." + holder.VerbRemove, Description: "Remove a tagged version (the builder), or for retention an untagged one by its digest (the artifact sweep; an index with the platform manifests no tagged or held index names, and never a manifest such an index names); says removed.",
Input: obj(map[string]any{"name": s("name"), "version": s("a tagged version"), "tag": s("its tag"), "digest": s("for retention"), "reason": s("why")}, "name", "reason"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
in, err := arg[reg.RemoveInput](args)
if err != nil {
return nil, err
@@ -317,10 +294,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: seat + "." + holder.VerbList, Description: "What the store holds of one repository, or of every repository the seat holds: each manifest's digest, whether it is an index and the manifests it names, the tags naming it, its size and whether it is held (ADR 0632 §3). Read from the store's own files; changes nothing.",
Input: obj(map[string]any{"name": s("<module>/<artifact>; every repository when not given")}),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
repos, err := v.List(context.Background(), seat, stdio.CallerOf(args), str(args, "name"))
if err != nil {
return nil, err
@@ -330,10 +303,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: seat + "." + holder.VerbHold, Description: "Keep one untagged bundle, archive or copied base from retention (the artifact sweep alone); says held.",
Input: obj(map[string]any{"name": s("name"), "digest": s("sha256:…"), "reason": s("which of the sweep's rules keeps it")}, "name", "digest", "reason"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
in, err := arg[reg.HoldInput](args)
if err != nil {
return nil, err
@@ -346,10 +315,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: seat + "." + holder.VerbRelease, Description: "Let a hold go (the artifact sweep, or the operator's session through registry_holds); says released.",
Input: obj(map[string]any{"name": s("name"), "digest": s("sha256:…"), "reason": s("why it is let go")}, "name", "digest", "reason"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
in, err := arg[reg.ReleaseInput](args)
if err != nil {
return nil, err
@@ -365,10 +330,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
out = append(out,
stdio.Tool{Name: holder.ImageSeat + "." + holder.VerbTags, Description: "The tags of an image.", Input: obj(map[string]any{"name": s("name")}, "name"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
a, err := v.Tags(holder.ImageSeat, stdio.CallerOf(args), str(args, "name"))
if err != nil {
return nil, err
@@ -378,10 +339,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: holder.ImageSeat + "." + holder.VerbMirror, Description: "Copy an outside image, pinned by its digest, into the store as upstream/<host>/<path> (every platform, only the parts it lacks): answers at once with the reference the copy will have, and says published (or refused, with why) on the seat's events when the copy ends; wait on that event (mesh-build-image alone).",
Input: obj(map[string]any{"source": s("<host>/<path>"), "digest": s("sha256:…")}, "source", "digest"),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
in, err := arg[reg.MirrorInput](args)
if err != nil {
return nil, err
@@ -392,15 +349,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
}
return reg.MirrorAnswer{Digest: in.Digest, Reference: ref}, nil
}},
stdio.Tool{Name: holder.CallersTool, Description: "Who may call which verb of distribution's seats: the trusted config value callers as mesh-config resolves it on this machine now (design 51's list where unset). The program asks it at each wake of the windows, as it has no config of its own.",
Input: obj(map[string]any{}),
Run: func(map[string]any) (any, error) {
c, err := callers()
if err != nil {
return nil, err
}
return map[string]any{"callers": c}, nil
}},
stdio.Tool{Name: "registry_health", Description: "distribution's health check, run by the runtime each minute: versions of each seat asked through the store's read side (the program's port 5100); fails naming the program when it does not answer.",
Input: obj(map[string]any{}),
Run: func(map[string]any) (any, error) {
@@ -410,10 +358,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: "registry_incoming", Description: "Open transfers of incoming and its fill; with drop, drops one by hand (a hand act, why required).",
Input: obj(map[string]any{"drop": s("a transfer id to drop"), "why": s("why it is dropped")}),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
idle, _ := v.DropIdle()
if id := str(args, "drop"); id != "" {
t, err := v.DropByHand(id, stdio.CallerOf(args), str(args, "why"))
@@ -456,10 +400,6 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
stdio.Tool{Name: "registry_holds", Description: "What is held, since when and for whom; with release, lets one go through the seat's release as the operator's session (a hand act).",
Input: obj(map[string]any{"seat": s("the seat"), "release": s("the name to release"), "digest": s("its digest"), "why": s("why")}),
Run: func(args map[string]any) (any, error) {
v, err := now()
if err != nil {
return nil, err
}
if name := str(args, "release"); name != "" {
if str(args, "why") == "" {
return nil, errors.New("a release by hand is a hand act: say why")
@@ -532,7 +472,7 @@ func main() {
Transfers: parts.BusTransfers[holder.Opening](holder.TransfersState), Cap: parts.BusCap(holder.IncomingObjects), Record: recordAct, Idle: parts.BusArm(holder.IdleState)}
files := &holder.Files{Container: or(os.Getenv("MESH_REGISTRY_CONTAINER"), "mesh-registry"), Run: holder.ExecRunner, UID: os.Getuid()}
v := &holder.Verbs{Holder: h, Catalog: store, Holds: kept[holder.Hold]{state: "holds", key: holder.Hold.Key}, Mirrors: mirrors{},
Files: files}
Files: files, SweepHolder: func() (string, error) { return seatlist.HolderOf(stdio.Ask, holder.SweepSeat) }}
go followMirrors(v)
go parts.FollowIdle(holder.IdleState, parts.IDs(h.Transfers), h.Expired, func(id string, dropped bool, err error) {
if err != nil {
@@ -542,7 +482,7 @@ func main() {
logf("transfer %s had no part for %s: dropped with its parts", id, holder.TransferIdle)
})
windows := kept[holder.Window]{state: "windows", key: func(w holder.Window) string { return w.Name }}
if err := stdio.Serve("", tools(v, windows, holder.ConfiguredCallers)); err != nil {
if err := stdio.Serve("", tools(v, windows)); err != nil {
logf("%v", err)
os.Exit(1)
}
@@ -0,0 +1,66 @@
package main
// The build modules the windows' driver counts (design 47 G §610, A-156, A-82): the modules that claim a build seat
// (`node-build-*`) in the catalog half, followed per watched branch by the seat mesh-catalog's client on the program's
// one views reader. No list of names is kept here, and none is configured.
import (
"encoding/json"
"slices"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
)
// buildSeatPrefix starts the name of every seat a build module claims.
const buildSeatPrefix = "node-build-"
// buildModulesOf are the modules among entries whose manifest claims a build seat, sorted.
func buildModulesOf(entries map[string]catalog.CatalogEntry) []string {
var out []string
for module, e := range entries {
claims, _ := e.Manifest["claims"].([]any)
for _, c := range claims {
if name, _ := c.(map[string]any)["name"].(string); strings.HasPrefix(name, buildSeatPrefix) {
out = append(out, module)
break
}
}
}
slices.Sort(out)
return out
}
// claimsBuildSeat says whether a module's entry, as the branch follower tells it, claims a build seat.
func claimsBuildSeat(module string, raw json.RawMessage) bool {
var e catalog.CatalogEntry
if json.Unmarshal(raw, &e) != nil {
return true // an entry not read may be one: the driver judges again
}
return len(buildModulesOf(map[string]catalog.CatalogEntry{module: e})) == 1
}
// branchesBuilds answers the driver's build modules from the catalog half of every followed branch; with a branch that has
// no first view yet it is an error naming it, never no build module.
type branchesBuilds struct{ b *meshcatalogclient.Branches }
func (h branchesBuilds) Modules() ([]string, error) {
if err := h.b.Ready(); err != nil {
return nil, err
}
all := map[string]catalog.CatalogEntry{}
for _, branch := range h.b.Followed() {
c, ok := h.b.Catalog(branch)
if !ok {
continue
}
half, _ := c.View() // a branch at none-yet is ready and empty (§614)
for m, e := range half.Entries {
if _, ok := all[m]; !ok {
all[m] = e
}
}
}
return buildModulesOf(all), nil
}
@@ -0,0 +1,37 @@
package main
import (
"encoding/json"
"reflect"
"testing"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
)
func entry(claims ...string) catalog.CatalogEntry {
var cs []any
for _, c := range claims {
cs = append(cs, map[string]any{"name": c, "scope": "node"})
}
return catalog.CatalogEntry{Manifest: map[string]any{"claims": cs}}
}
// Design 47 G §610, A-156: the build modules the driver counts are the modules claiming a `node-build-*` seat in the
// catalog half, whatever their names; no list of names is kept.
func TestBuildModulesAreTheClaimantsOfBuildSeats(t *testing.T) {
entries := map[string]catalog.CatalogEntry{
"a-compiler": entry("node-build-go"), "an-imager": entry("node-build-image", "something"),
"gitea": entry("git"), "mesh-build-go": entry("not-a-build-seat"), "no-claims": {Manifest: map[string]any{}},
}
if got, want := buildModulesOf(entries), []string{"a-compiler", "an-imager"}; !reflect.DeepEqual(got, want) {
t.Errorf("build modules = %v, want %v", got, want)
}
raw, _ := json.Marshal(entry("node-build-node"))
if !claimsBuildSeat("m", raw) {
t.Error("an entry claiming a build seat is not told as one")
}
raw, _ = json.Marshal(entry("git"))
if claimsBuildSeat("m", raw) {
t.Error("an entry claiming no build seat wakes the driver")
}
}
+24 -2
View File
@@ -21,6 +21,7 @@ import (
"time"
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/views"
"github.com/nats-io/nats.go/jetstream"
@@ -82,8 +83,29 @@ func run(ctx context.Context, getenv func(string) string) error {
return err
}
defer w.Close()
go drive(ctx, js, cred, &holder.Driver{Delivery: busDelivery{calls}, State: kvWindows{windows}, Callers: busCallers{calls}.Read,
Closed: sayClosed(js, cred), Finding: findings.Say}, logger)
// Which modules are the build modules is the catalog half's (design 47 G §610, A-156): the branch follower of the seat
// mesh-catalog's client on a views reader that shares the program's one events hub. The driver wakes again when a
// build module's entry comes, changes or goes, so the start's wake, made before the first view, is not the last.
hub := process.NewEventHub(logger.Printf)
reader := &views.ProcessReader{Module: holder.Self, Call: calls.Call, Logf: logger.Printf, Hub: hub}
branches, err := meshcatalogclient.FollowBranchesProcess(reader, calls.Call)
if err != nil {
return err
}
branches.Logf = logger.Printf
catalogMoved := make(chan struct{}, 1)
branches.Changed = func(_, module string, entry json.RawMessage, deleted bool, _ time.Time) {
if !deleted && !claimsBuildSeat(module, entry) {
return
}
select {
case catalogMoved <- struct{}{}:
default:
}
}
reader.Start(ctx, js, cred, nil)
go drive(ctx, js, cred, hub, catalogMoved, &holder.Driver{Delivery: busDelivery{calls}, State: kvWindows{windows},
BuildModules: branchesBuilds{branches}.Modules, Closed: sayClosed(js, cred), Finding: findings.Say}, logger)
}
front := &holder.Front{Store: store, Window: &open, Logf: logger.Printf,
Published: func(name, tag string) (bool, error) {
@@ -7,8 +7,8 @@ package main
// OCI store on the loopback, the status view's node_haves and the node-engine seat's manifest-reads answered on their
// call subjects. An away node holds the old name open; its return (manifest-reads-changed) closes it, said once on
// distribution.events, after which a direct write is refused naming publish; reads stay open; a waiter on a seat's
// `published` hears none of the windows' events; and the build modules counted are publish's callers as the tools
// bundle answers the config value, not design 51's defaults.
// `published` hears none of the windows' events; and the build modules counted are the modules claiming a `node-build-*` seat
// in the catalog half the seat mesh-catalog answers (design 47 G §610, A-156), not a list of names.
import (
"context"
@@ -29,6 +29,8 @@ import (
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/testbus"
"distribution/internal/holder"
@@ -56,7 +58,8 @@ func TestDistributionWhole(t *testing.T) {
t.Fatal(err)
}
if _, err := js.CreateOrUpdateConsumer(ctx, "EVENTS", jetstream.ConsumerConfig{Durable: "novox_distribution", AckPolicy: jetstream.AckExplicitPolicy,
FilterSubjects: []string{"mesh.mod.mesh-delivery-view.event.delivered", "mesh.seat.node-engine.event.manifest-reads-changed.*"}}); err != nil {
FilterSubjects: []string{"mesh.mod.mesh-delivery-view.event.delivered", "mesh.seat.node-engine.event.manifest-reads-changed.*",
"mesh.seat.mesh-catalog.event.catalog-revision.>", "mesh.seat.mesh-catalog.event.registration-changed", "mesh.seat.mesh-delivery.event.source-moved"}}); err != nil {
t.Fatal(err)
}
@@ -77,21 +80,36 @@ func TestDistributionWhole(t *testing.T) {
answer(m, map[string]any{"keys": map[string]any{
"ace.mesh-build-go": map[string]any{"fp": "fp-new"}, "g14.mesh-build-go": map[string]any{"fp": fp},
"ace.gitea": map[string]any{"fp": "x"}, "g14.gitea": map[string]any{"fp": "x"},
// A build module the operator took off publish's list: it holds no window (the caller list as configured).
// A module claiming no build seat in the catalog half: it holds no window.
"g14.mesh-build-node": map[string]any{"fp": "fp-node-old"},
}})
}); err != nil {
t.Fatal(err)
}
// distribution's own tools bundle answers the config value callers as mesh-config resolved it: publish by
// mesh-build-go alone. The program reads it there, never from an environment variable nothing renders.
if _, err := nc.Subscribe("mesh.mod.distribution.>", func(m *nats.Msg) {
if !strings.Contains(m.Subject, holder.CallersTool) {
return
// The catalog half, as the seats mesh-catalog and mesh-delivery answer it: one watched branch, main, whose trunk revision
// has mesh-build-go claiming the build seat node-build-go; mesh-build-node claims no build seat, so it holds no window.
entryOf := func(module string, claims ...string) catalog.CatalogEntry {
var cs []any
for _, c := range claims {
cs = append(cs, map[string]any{"name": c, "scope": "node"})
}
return catalog.CatalogEntry{Module: module, Manifest: map[string]any{"claims": cs}}
}
catalogHalf := func(m *nats.Msg) {
switch {
case strings.Contains(m.Subject, ".call.registrations."):
answer(m, catalog.RegistrationsAnswer{Registrations: []catalog.Registration{{Module: "mesh-build-go", Branches: []string{"main"}}}})
case strings.Contains(m.Subject, ".call.revision."):
answer(m, catalog.RevisionAnswer{Revision: &catalog.CatalogRevision{Revision: 1, Kind: meshcatalogclient.KindTrunk, Branch: "main"},
Entries: []catalog.CatalogEntry{entryOf("mesh-build-go", "node-build-go"), entryOf("mesh-build-node"), entryOf("gitea", "git")}})
case strings.Contains(m.Subject, ".call.source."):
answer(m, map[string]any{"branches": []string{"main"}})
}
}
for _, subject := range []string{"mesh.seat.mesh-catalog.>", "mesh.seat.mesh-delivery.call.>"} {
if _, err := nc.Subscribe(subject, catalogHalf); err != nil {
t.Fatal(err)
}
answer(m, map[string]any{"callers": map[string]any{"publish": []string{"mesh-build-go"}}})
}); err != nil {
t.Fatal(err)
}
engine := func(node string) {
for _, subj := range []string{"mesh.seat.node-engine.call.manifest-reads." + node + ".>", "mesh.seat.node-engine.tool.manifest-reads." + node} {
@@ -27,8 +27,6 @@ import (
const (
havesAsk = "mesh-delivery-view.node_haves"
readsAsk = "seat:node-engine.manifest-reads"
// callersAsk is distribution's own tool answering its config value callers as resolved.
callersAsk = holder.Self + "." + holder.CallersTool
)
// caller is a process's call of a tool or a seat verb.
@@ -37,25 +35,6 @@ type caller interface {
CallOn(node, key string, body any) (json.RawMessage, error)
}
// busCallers reads the caller list from distribution's own tools bundle (holder.CallersTool, manifest invokes): a
// program has no runtime that resolves its module's config values, the tools bundle has (stdio.Config), so the program
// asks it on each wake rather than read an environment variable nothing renders.
type busCallers struct{ c caller }
func (b busCallers) Read() (holder.Callers, error) {
raw, err := b.c.Call(callersAsk, map[string]any{})
if err != nil {
return nil, fmt.Errorf("%s: %w", callersAsk, err)
}
var a struct {
Callers json.RawMessage `json:"callers"`
}
if err := json.Unmarshal(unwrap(raw), &a); err != nil || len(a.Callers) == 0 {
return nil, fmt.Errorf("%s answered what is not a caller list: %s", callersAsk, unwrap(raw))
}
return holder.ParseCallers(string(a.Callers))
}
// busDelivery answers holder.Delivery through the bus.
type busDelivery struct{ c caller }
@@ -203,7 +182,7 @@ func wakes(subject string) (string, bool) {
// drive wakes the driver at start and on each waking event of the program's durable consumer of the events stream.
// A wake that could not judge is said and its event asked again a minute later.
func drive(ctx context.Context, js jetstream.JetStream, cred process.Account, d *holder.Driver, logger *log.Logger) {
func drive(ctx context.Context, js jetstream.JetStream, cred process.Account, hub *process.EventHub, catalogMoved <-chan struct{}, d *holder.Driver, logger *log.Logger) {
wake := func(why string) error {
said, err := d.Wake(why)
for _, s := range said {
@@ -215,9 +194,9 @@ func drive(ctx context.Context, js jetstream.JetStream, cred process.Account, d
return err
}
_ = wake("distribution started")
// The module's one durable on the events stream is pulled by the events hub (mesh-sdk process), each waking event
// registered by its subject pattern: the seat of the view says delivered, the node-engine says a manifest read changed.
hub := process.NewEventHub(logger.Printf)
// The module's one durable on the events stream is pulled by the events hub (mesh-sdk process), which the views reader
// of the catalog half shares: each waking event registered by its subject pattern, the seat of the view saying
// delivered, the node-engine saying a manifest read changed.
handle := func(m jetstream.Msg) {
why, ok := wakes(m.Subject())
if !ok {
@@ -240,6 +219,13 @@ func drive(ctx context.Context, js jetstream.JetStream, cred process.Account, d
logger.Printf("the events stream cannot be heard (%v): the windows move only at the next start", err)
return
}
<-ctx.Done()
hub.Stop()
for {
select {
case <-catalogMoved:
_ = wake("a build module moved in the catalog half")
case <-ctx.Done():
hub.Stop()
return
}
}
}