W16.2: mesh-config reads which modules exist, their manifests and a build's manifest from the catalog half through meshcatalogclient's branch follower (milestone 47/G)

Builds: design 47 G §196, §594, §614; lead answers A-5, A-15

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 23:34:28 +01:00
parent 7278e509f4
commit da5bbe0a17
15 changed files with 453 additions and 193 deletions
@@ -0,0 +1,68 @@
package main
// mesh-config's reading of each module's declarations (design 47 G §196, §594, §614): the catalog
// half, taken through meshcatalogclient's follower of the watched branches on the part's one views reader. A module's
// manifest is the one its catalog graph entry carries whole (§443), never the indexer's state `modules` or `manifests`; the
// manifest a running build was made from is the entry read by that build's fingerprint (§614, an older build's entry).
import (
"context"
"fmt"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
)
// CatalogHalf is what the holder reads of the catalog half.
type CatalogHalf interface {
// Entry is a module's entry at the trunk; an error while a watched branch has no first view yet, never "no module".
Entry(module string) (catalog.CatalogEntry, bool, error)
// Ready is nil once every watched branch has its first view (none-yet counting as one), else says what it waits for.
Ready() error
// ByFingerprint reads an entry by the fingerprint of a build: from a branch's half when one lists it, else asked of the
// seat's verb `entry` once and kept (asked, answered or not found).
ByFingerprint(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState)
}
// branchesHalf is the CatalogHalf over the follower of the watched branches.
type branchesHalf struct{ b *meshcatalogclient.Branches }
func (h branchesHalf) Ready() error {
followed := h.b.Followed()
if len(followed) == 0 {
return fmt.Errorf("the catalog half names no watched branch yet (the seat mesh-catalog's registrations are asked until they answer)")
}
var waiting []string
for _, branch := range followed {
if c, ok := h.b.Catalog(branch); ok {
select {
case <-c.Ready():
default:
waiting = append(waiting, branch)
}
}
}
if len(waiting) > 0 {
return fmt.Errorf("the catalog half has no first view of %s yet", strings.Join(waiting, ", "))
}
return nil
}
func (h branchesHalf) Entry(module string) (catalog.CatalogEntry, bool, error) {
if err := h.Ready(); err != nil {
return catalog.CatalogEntry{}, false, err
}
for _, branch := range h.b.Followed() { // the branch followed first names a module more than one hold
if c, ok := h.b.Catalog(branch); ok {
if e, ok := c.Entry(module); ok {
return e, true, nil
}
}
}
return catalog.CatalogEntry{}, false, nil
}
func (h branchesHalf) ByFingerprint(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) {
return h.b.Resolve(ctx, fingerprint)
}
@@ -0,0 +1,90 @@
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-sdk/go/views"
)
// wiringOf is a part's reader in miniature: it keeps the followers adopted, so a test starts them as the reader's Start does.
type wiringOf struct {
mu sync.Mutex
fs []*views.Follower
}
func (w *wiringOf) Adopt(f *views.Follower, _, _ string, _ func(context.Context, []byte) error) error {
w.mu.Lock()
defer w.mu.Unlock()
w.fs = append(w.fs, f)
return nil
}
func (w *wiringOf) Drop(string, string) error { return nil }
// Design 47 G §196, §594: which modules exist, and the manifest of each, are the catalog half's; a branch with no first
// view yet is an error naming it, never no module, and an entry's manifest is the one it carries whole.
func TestCatalogHalfIsTheWatchedBranchesNewestRevision(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
answered := false
var mu sync.Mutex
call := func(key string, _ any) (json.RawMessage, error) {
mu.Lock()
defer mu.Unlock()
switch key {
case meshcatalogclient.RegistrationsVerb:
return json.Marshal(catalog.RegistrationsAnswer{Registrations: []catalog.Registration{{Module: "m", Version: 1, Branches: []string{"main"}}}})
case meshcatalogclient.SourceVerb:
return json.Marshal(delivery.SourceAnswer{})
case meshcatalogclient.RevisionVerb:
if !answered {
return nil, fmt.Errorf("the holder is away")
}
return json.Marshal(catalog.RevisionAnswer{Revision: &catalog.CatalogRevision{Revision: 1, Kind: meshcatalogclient.KindTrunk},
Entries: []catalog.CatalogEntry{{Module: "pg", Manifest: map[string]any{"module": "pg"}}}})
}
return nil, fmt.Errorf("asked %s", key)
}
w := &wiringOf{}
b, err := meshcatalogclient.NewBranches(w, call)
if err != nil {
t.Fatal(err)
}
h := branchesHalf{b}
if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no watched branch") {
t.Fatalf("before the registrations: %v, want an error naming the missing branch", err)
}
go func() { _ = w.fs[0].Start(ctx) }() // the registrations
for len(b.Followed()) == 0 {
select {
case <-ctx.Done():
t.Fatal("the registrations' branch is never followed")
case <-time.After(10 * time.Millisecond):
}
}
if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no first view of main") {
t.Fatalf("with no first view: %v, want an error naming the branch, never no module", err)
}
mu.Lock()
answered = true
mu.Unlock()
c, _ := b.Catalog("main")
if err := c.Start(ctx); err != nil {
t.Fatal(err)
}
got, ok, err := h.Entry("pg")
if err != nil || !ok || got.Manifest["module"] != "pg" || h.Ready() != nil {
t.Fatalf("with the first view: %v, %v, %v", got, ok, err)
}
if _, ok, err := h.Entry("absent"); ok || err != nil {
t.Fatalf("a module the half does not list: %v, %v", ok, err)
}
}
@@ -98,6 +98,9 @@ type Config struct {
Forge PullForge
// Delivery is what the delivery parts answer (renames.go, retiring.go); nil, no window closes and nothing retires.
Delivery Delivery
// Catalog is the catalog half: which modules exist and the manifest each entry carries, and the entry of a build's
// fingerprint (design 47 G §196, §594).
Catalog CatalogHalf
// Vault is the seat mesh-vault, for the retiring of a secret; nil, a secret does not retire.
Vault Vault
// RouterWithin and RouterEvery bound how long a proposal waits for the router's word on its ask, and how often
@@ -1,6 +1,7 @@
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -8,7 +9,6 @@ import (
meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"sort"
"strings"
"sync"
@@ -27,10 +27,34 @@ type memStore struct {
handActs []string
// failHandAct makes the hand-act log refuse.
failHandAct bool
// entries are the catalog half's trunk entries by module, and byFp the entries a build's fingerprint names: the memStore
// is also the CatalogHalf the tests give (catalogfeed.go).
entries map[string]catalog.CatalogEntry
byFp map[string]catalog.CatalogEntry
notYet error
}
func (m *memStore) Entry(module string) (catalog.CatalogEntry, bool, error) {
m.mu.Lock()
defer m.mu.Unlock()
e, ok := m.entries[module]
return e, ok, m.notYet
}
func (m *memStore) Ready() error { return m.notYet }
func (m *memStore) ByFingerprint(_ context.Context, fp string) (catalog.CatalogEntry, catalogclient.EntryState) {
m.mu.Lock()
defer m.mu.Unlock()
if e, ok := m.byFp[fp]; ok {
return e, catalogclient.EntryAnswered
}
return catalog.CatalogEntry{}, catalogclient.EntryNotFound
}
func newStore() *memStore {
return &memStore{states: map[string]map[string][]byte{}, logs: map[string][]LogEntry{}}
return &memStore{states: map[string]map[string][]byte{}, logs: map[string][]LogEntry{}, entries: map[string]catalog.CatalogEntry{},
byFp: map[string]catalog.CatalogEntry{}}
}
func (m *memStore) Get(state, key string) (*Entry, error) {
@@ -206,38 +230,31 @@ func (m *memStore) keysOf(state string) []string {
return keys
}
// manifest keeps a manifest in the indexer's manifests by its digest, as the indexer writes it, and answers the digest.
func (m *memStore) manifest(t *testing.T, module, raw string) string {
// entryOf is a module's catalog entry carrying the manifest raw whole, with its digest and commit.
func entryOf(t *testing.T, module, raw, fp string) catalog.CatalogEntry {
t.Helper()
if !json.Valid([]byte(raw)) {
var whole map[string]any
if err := json.Unmarshal([]byte(raw), &whole); err != nil {
t.Fatalf("not JSON: %s", raw)
}
digest := catalogclient.ManifestDigest([]byte(raw))
value, err := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: module, Manifest: raw})
if err != nil {
t.Fatal(err)
}
m.mirror(t, ManifestsRead, digest, string(value))
return digest
return catalog.CatalogEntry{Module: module, Fp: fp, Manifest: whole, ManifestDigest: catalogclient.ManifestDigest([]byte(raw)), Commit: "c1"}
}
// declare makes raw the module's manifest at the trunk: kept by digest, and named by the indexer's modules.<module>.
// declare makes raw the module's manifest at the trunk: the entry the catalog half holds of it.
func (m *memStore) declare(t *testing.T, module, raw string) {
t.Helper()
digest := m.manifest(t, module, raw)
m.mirror(t, ModulesRead, module, fmt.Sprintf(`{"v":1,"manifest":%q,"repository":"novox/%s","commit":"c1"}`,
digest, module))
m.mu.Lock()
defer m.mu.Unlock()
m.entries[module] = entryOf(t, module, raw, "")
}
// want makes a node's want name a build of the module whose manifest is raw, as the assigner and the builder keep them.
// want makes a node's want name a build of the module whose manifest is raw, as the assigner keeps it and the catalog half
// holds the entry of that build's fingerprint.
func (m *memStore) want(t *testing.T, node, module, fp, raw string) {
t.Helper()
digest := m.manifest(t, module, raw)
key, err := builds.BuilderBuildKey(module, fp)
if err != nil {
t.Fatal(err)
}
m.mirror(t, BuildsRead, key, fmt.Sprintf(`{"v":1,"state":"done","manifest":%q}`, digest))
m.mu.Lock()
m.byFp[fp] = entryOf(t, module, raw, fp)
m.mu.Unlock()
m.mirror(t, WantsRead, "want."+node+"."+module, fmt.Sprintf(`{"v":1,"fp":%q,"self":true}`, fp))
}
@@ -280,7 +297,7 @@ func aMesh(t *testing.T) (*Config, *memStore) {
m.assign(t, "g14", "claude-code")
m.assign(t, "g14", "old")
clock := time.Date(2026, 10, 14, 19, 0, 0, 0, time.UTC)
c := &Config{Store: m, Now: func() time.Time { clock = clock.Add(time.Second); return clock }, Logf: t.Logf}
c := &Config{Store: m, Catalog: m, Now: func() time.Time { clock = clock.Add(time.Second); return clock }, Logf: t.Logf}
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
@@ -712,21 +729,32 @@ func TestDeclarationsComeFromTheManifestsAsBuilt(t *testing.T) {
m.mirror(t, WantsRead, "want.novox.de-spiegel", `{"v":1,"removed":true}`)
},
resolved: map[string]string{"de-spiegel.praktijk-name": "unset "}, absent: []string{"de-spiegel.old-key"}},
{name: "a placed module the indexer holds no manifest of is unresolved, said, its values kept",
{name: "a placed module the catalog half holds no entry of is unresolved, said, its values kept",
given: func(t *testing.T, m *memStore) {
must(t, m.Put(ResolvedState, ResolvedKey("novox", "de-spiegel", "praktijk-name"),
Resolved{Node: "novox", Module: "de-spiegel", Key: "praktijk-name", Source: SourceUnset}))
m.assign(t, "novox", "de-spiegel")
},
resolved: map[string]string{"de-spiegel.praktijk-name": "unset "},
unresolved: "the indexer holds no modules.de-spiegel"},
unresolved: "the catalog half holds no entry of de-spiegel"},
// With no first view of the catalog half a module is never read as absent: the error names the view (design 47 G §196).
{name: "a catalog half with no first view leaves its module unresolved, said, its values kept",
given: func(t *testing.T, m *memStore) {
m.declare(t, "de-spiegel", despiegel)
m.notYet = errors.New("the catalog half has no first view of main yet")
must(t, m.Put(ResolvedState, ResolvedKey("novox", "de-spiegel", "praktijk-name"),
Resolved{Node: "novox", Module: "de-spiegel", Key: "praktijk-name", Source: SourceUnset}))
m.assign(t, "novox", "de-spiegel")
},
resolved: map[string]string{"de-spiegel.praktijk-name": "unset "},
unresolved: "no first view of main"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
m := newStore()
tc.given(t, m)
var said []string
c := &Config{Store: m, Now: time.Now, Logf: func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) }}
c := &Config{Store: m, Catalog: m, Now: time.Now, Logf: func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) }}
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
@@ -21,6 +21,7 @@ import (
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/views"
)
@@ -137,6 +138,36 @@ func main() {
default:
}
}, w)
// Which modules exist, each one's manifest and a build's are the catalog half's, followed per watched branch by the
// seat's client on the same reader (design 47 G §196, §614).
branches, err := catalogclient.FollowBranches(theViews.reader, stdio.Ask)
if err != nil {
logf("%v", err)
os.Exit(1)
}
branches.Logf = logf
branches.Changed = func(string, string, json.RawMessage, bool, time.Time) {
select {
case moved <- struct{}{}:
default:
}
}
branches.Entries.Changed = func(catalogclient.EntryRow) {
select {
case moved <- struct{}{}:
default:
}
}
c.Catalog = branchesHalf{branches}
theViews.catalog = func() []*views.Follower {
out := []*views.Follower{branches.Registrations.Follower}
for _, b := range branches.Followed() {
if cat, ok := branches.Catalog(b); ok {
out = append(out, cat.Follower)
}
}
return out
}
// the runtime answers the writer's subscription only once Serve has its channel
go func() {
<-stdio.Served()
@@ -23,6 +23,8 @@ func TestEveryStateAndActHasItsTool(t *testing.T) {
Packages map[string]string `json:"packages"`
Logs []string `json:"logs"`
Reads []string `json:"reads"`
Invokes []string `json:"invokes"`
Consumes []string `json:"consumes"`
Tools []string `json:"tools"`
Claims []struct {
Name string `json:"name"`
@@ -32,16 +34,24 @@ func TestEveryStateAndActHasItsTool(t *testing.T) {
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
// The builder's keys are read through its published package, taken by its series (ADR 0634 §1).
if m.Packages["git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"] != "0.1" {
t.Errorf("packages %v: want builds of series 0.1", m.Packages)
// Which modules exist and a build's manifest are the catalog half's (design 47 G §594): no builder package, and the
// manifest names neither the indexer's modules and manifests nor the builder's builds.
if len(m.Packages) != 0 {
t.Errorf("packages %v: the builder's keys are no longer read", m.Packages)
}
for _, list := range [][]string{m.Reads, m.Invokes, m.Consumes} {
for _, e := range list {
if strings.Contains(e, "mesh-delivery-builder") || strings.HasSuffix(e, "indexer_read_modules") || strings.HasSuffix(e, "indexer_read_manifests") ||
strings.HasSuffix(e, "views.modules") || strings.HasSuffix(e, "views.manifests") {
t.Errorf("the manifest names %s, which only the catalog half answers now", e)
}
}
}
via := map[string]string{
ValuesState: "config list", ResolvedState: "config list", PredefinedState: "config list",
ProposalsState: "config proposals", PullsState: "config list", KeptState: "config list", HistoryLog: "config history",
DeclaredState: DeclaredTool,
AssignmentsRead: "config list", WantsRead: "config list", BuildsRead: "config list", ModulesRead: DeclaredTool,
ManifestsRead: DeclaredTool, IndexerPredefinedRead: "config list", CandidatesRead: ResyncTool,
AssignmentsRead: "config list", WantsRead: "config list", IndexerPredefinedRead: "config list", CandidatesRead: ResyncTool,
"a resolution": ResyncTool, "the indexer's candidates taken": ResyncTool,
// design 32: each new state with the tool that reads it, each act with the tool that starts it.
RenamesState: RenamesTool, RetiringState: RetiringTool, FindingsState: "config_renames and config_retiring",
@@ -80,10 +90,10 @@ func TestEveryStateAndActHasItsTool(t *testing.T) {
t.Errorf("the manifest lists the tool %s, which is not served", name)
}
}
// The delivery owners' state is read through their tools and views (ADR 0615 §1): only the builder's builds, which has
// no tool yet, is still a read.
if len(m.Reads) != 1 || m.Reads[0] != BuildsRead {
t.Errorf("reads %v, want the builder's builds alone", m.Reads)
// The delivery owners' state is read through their tools and views (ADR 0615 §1), and which modules exist through the
// catalog half (design 47 G §196): the manifest reads no state of theirs.
if len(m.Reads) != 0 {
t.Errorf("reads %v, want none", m.Reads)
}
for _, list := range [][]string{m.State, m.Logs, m.Reads} {
for _, s := range list {
@@ -1,14 +1,15 @@
package main
// What mesh-config reads of the delivery owners' state, through their tools and views families (novox/hq ADR 0615 §1,
// §4 to §6, ADR 0596): never a bucket of theirs. For each of the six states it follows (the indexer's modules, manifests,
// predefined and config-candidates, the assigner's assignments and wants) it asks the owner's read tool for the whole
// §4 to §6, ADR 0596): never a bucket of theirs. For each of the four states it follows (the indexer's predefined and
// config-candidates, the assigner's assignments and wants) it asks the owner's read tool for the whole
// state at its start, keeps the picture from the owner's `views.<state>` events, and asks again only on a gap. Started
// while an owner is down it keeps asking, and past two minutes writes a finding naming the owner and the tool.
//
// The ordinary read tools are asked, not the first-view tools: those answer a few named callers (mesh-grants,
// node-tools, the delivery parts), and mesh-config is not one. The builder's `builds` has no owner tool yet (A2 built the
// indexer's and the assigner's), so it is still read as a `reads` entry.
// node-tools, the delivery parts), and mesh-config is not one. Which modules exist, and each one's manifest, are not the
// indexer's: they are the catalog half's, followed per watched branch by meshcatalogclient on the same reader (design 47 G
// §196, §594, §614; catalogfeed.go).
import (
"context"
@@ -25,8 +26,6 @@ import (
// The tools of the owners that mesh-config asks, by the state they read.
const (
IndexerModulesTool = "indexer_read_modules"
IndexerManifestsTool = "indexer_read_manifests"
IndexerPredefTool = "indexer_read_predefined"
IndexerCandidatesTool = "indexer_read_config_candidates"
AssignerAssignmentsTool = "assigner_read_assignments"
@@ -39,8 +38,6 @@ const (
// followed is each state read through its owner: the `<owner>.<state>` name this module's code uses, the owner, its
// state and its tool.
var followed = []struct{ Read, Owner, State, Tool string }{
{ModulesRead, indexerModule, "modules", IndexerModulesTool},
{ManifestsRead, indexerModule, "manifests", IndexerManifestsTool},
{IndexerPredefinedRead, indexerModule, catalog.StatePredefined, IndexerPredefTool},
{CandidatesRead, indexerModule, catalog.StateConfigCandidates, IndexerCandidatesTool},
{AssignmentsRead, assignerModule, "assignments", AssignerAssignmentsTool},
@@ -53,6 +50,9 @@ var followed = []struct{ Read, Owner, State, Tool string }{
type ownerViews struct {
by map[string]*views.Follower
reader *views.Reader
// catalog is the followers of the catalog half (the registrations and each watched branch's revision view), which the
// first-view tools list and ask again beside the owners' (design 47 G §614); nil before they are followed.
catalog func() []*views.Follower
}
// newOwnerViews makes a follower for each state; changed is told when any picture changes. findings is the module's writer,
@@ -137,26 +137,43 @@ func (o *ownerViews) keys(state string) ([]string, bool, error) {
return out, true, nil
}
// statuses are the first views' statuses, by owner and state.
// catalogFollowers are the catalog half's followers, none before they are followed.
func (o *ownerViews) catalogFollowers() []*views.Follower {
if o.catalog == nil {
return nil
}
return o.catalog()
}
// statuses are the first views' statuses, by owner and state: the owners', then the catalog half's.
func (o *ownerViews) statuses() []views.Status {
var out []views.Status
for _, f := range followed {
out = append(out, o.by[f.Read].Status())
}
for _, f := range o.catalogFollowers() {
out = append(out, f.Status())
}
return out
}
// again asks one first view once more by hand ("<owner>.<state>", or all when empty).
func (o *ownerViews) again(ctx context.Context, which, why string) ([]string, error) {
var done []string
all := make([]*views.Follower, 0, len(followed))
for _, f := range followed {
if which != "" && which != f.Owner+"."+f.State {
all = append(all, o.by[f.Read])
}
all = append(all, o.catalogFollowers()...)
for _, f := range all {
name := f.Owner + "." + f.State
if which != "" && which != name {
continue
}
if err := o.by[f.Read].Again(ctx, why); err != nil {
if err := f.Again(ctx, why); err != nil {
return done, err
}
done = append(done, f.Owner+"."+f.State)
done = append(done, name)
}
if len(done) == 0 {
return nil, errors.New("no first view is named " + which)
@@ -168,8 +185,9 @@ func (o *ownerViews) again(ctx context.Context, which, why string) ([]string, er
func viewTools(c *Config, o func() *ownerViews) []stdio.Tool {
return []stdio.Tool{
{Name: FirstViewsTool,
Description: "mesh-config's first views of the delivery owners' state: for each of the indexer's modules, manifests, " +
"predefined and config-candidates and the assigner's assignments and wants, the tool it asks, the revision its " +
Description: "mesh-config's first views of the delivery owners' state: for each of the indexer's " +
"predefined and config-candidates, the assigner's assignments and wants and the catalog half's registrations and " +
"each watched branch's revision, the tool it asks, the revision its " +
"picture holds, when it asked and was answered, how many asks (and by hand), and whether its condition is raised.",
Run: func(map[string]any) (any, error) {
ov := o()
@@ -39,8 +39,12 @@ func TestAFollowedStateIsReadFromItsOwnersFirstViewAndEvents(t *testing.T) {
if !o.reader.Follows("mesh-delivery-assigner.wants") {
t.Error("the wants view is not one of the reader's (A-28)")
}
if _, followed, _ := o.get(BuildsRead, "x"); followed {
t.Error("the builder's builds is followed, and it has no owner tool")
// The indexer's modules and manifests and the builder's builds are no states this module follows: which modules exist is
// the catalog half's (design 47 G §196).
for _, gone := range []string{"mesh-delivery-indexer.modules", "mesh-delivery-indexer.manifests", "mesh-delivery-builder.builds"} {
if _, followed, _ := o.get(gone, "x"); followed {
t.Errorf("%s is followed", gone)
}
}
ctx := context.Background()
go func() { _ = o.by[WantsRead].Start(ctx) }()
@@ -80,7 +80,7 @@ func aRenamingMesh(t *testing.T) (*Config, *memStore, *fakeDelivery, *time.Time)
}
clock := time.Date(2026, 11, 4, 10, 0, 0, 0, time.UTC)
d := &fakeDelivery{haves: map[string]string{"g14.player": fp1, "novox.player": fp1}, notSays: map[string]bool{}}
c := &Config{Store: m, Now: func() time.Time { return clock }, Logf: t.Logf, Delivery: d, Findings: m}
c := &Config{Store: m, Catalog: m, Now: func() time.Time { return clock }, Logf: t.Logf, Delivery: d, Findings: m}
if _, err := c.Reconcile(); err != nil {
t.Fatal(err)
}
@@ -275,7 +275,7 @@ func TestAWindowHeldSevenDaysIsAFindingAndAHandCloseIsAHandAct(t *testing.T) {
}
m.handActs = nil
// A restart in between: the module holds no finding of its own, the writer holds it (design 47 G A-32, A-38).
c = &Config{Store: c.Store, Now: c.Now, Logf: c.Logf, Delivery: c.Delivery, Findings: c.Findings}
c = &Config{Store: c.Store, Catalog: c.Catalog, Now: c.Now, Logf: c.Logf, Delivery: c.Delivery, Findings: c.Findings}
if _, err := c.RenameClose("player", "level", "novox", "novox is retired for good"); err != nil {
t.Fatal(err)
}
@@ -4,8 +4,6 @@ import (
"encoding/json"
"fmt"
meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"sort"
"strings"
"time"
@@ -29,12 +27,6 @@ const (
AssignmentsRead = (assignerModule + "." + meshseat.StateAssignments)
// WantsRead is the assigner's wants, `want.<node>.<module>`: the build each node is to run (ADR 0539 §1).
WantsRead = assignerModule + ".wants"
// BuildsRead is the builder's builds, `<module>.<fingerprint hex>`: each build's manifest digest (ADR 0525).
BuildsRead = builds.BuilderModule + ".builds"
// ModulesRead is the indexer's modules, `<module>`: the trunk's manifest digest of each module, from whichever
// repository holds it (ADR 0437 §5, ADR 0478 §9); ManifestsRead its manifests by digest (ADR 0429 §16).
ModulesRead = indexerModule + "." + catalog.StateModules
ManifestsRead = indexerModule + "." + catalog.StateManifests
// HistoryLog is the log of every set and clear, with the proven caller and why (ADR 0348 §9).
HistoryLog = "history"
+57 -74
View File
@@ -1,12 +1,13 @@
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"sort"
"strings"
)
@@ -91,9 +92,9 @@ func (c *Config) readAssignments(s *Sources) error {
return nil
}
// readDeclarations reads each placed module's declarations from its manifests as the delivery holds them: the trunk's
// (the indexer's `modules.<module>` and its manifest by digest), and, on each node whose want names a build, that
// build's (the builder's `<module>.<fp>` names its manifest digest). On a node the want's build is laid over the
// readDeclarations reads each placed module's declarations from its manifests as the catalog half holds them: the trunk's
// (the manifest its catalog graph entry carries), and, on each node whose want names a build, that build's (the entry
// of its fingerprint). On a node the want's build is laid over the
// trunk's, key by key, so a key only the build it runs declares still resolves there and a key the trunk adds resolves
// before its build is wanted; Declared is the trunk's with every key only a wanted build declares. A placed module
// none of whose manifests can be read is Unresolved, with why.
@@ -186,90 +187,72 @@ func modulesOf(as []Assignment) []string {
return out
}
// trunkDeclarations is a module's declarations at the trunk: the manifest the indexer's `modules.<module>` names.
// trunkDeclarations is a module's declarations at the trunk: the manifest its catalog graph entry carries whole.
func (c *Config) trunkDeclarations(module string) (ModuleDeclarations, error) {
key, err := catalogclient.ModulesKey(module)
e, err := c.trunkEntry(module)
if err != nil {
return ModuleDeclarations{}, err
}
e, err := c.Store.Get(ModulesRead, key)
if err != nil {
return ModuleDeclarations{}, fmt.Errorf("the indexer's modules.%s cannot be read: %w", module, err)
}
if e == nil {
return ModuleDeclarations{}, fmt.Errorf("the indexer holds no modules.%s, so no manifest of it is known", module)
}
var m struct {
Manifest string `json:"manifest"`
}
if err := json.Unmarshal(e.Value, &m); err != nil || m.Manifest == "" {
return ModuleDeclarations{}, fmt.Errorf("the indexer's modules.%s names no manifest", module)
}
return c.manifestDeclarations(module, m.Manifest)
return c.entryDeclarations(e)
}
// builtDeclarations is a module's declarations at a build: the manifest the builder's done key names.
// trunkEntry is a module's entry in the catalog half; with no first view of it, or none listing the module, an error naming
// it, never no declarations.
func (c *Config) trunkEntry(module string) (catalog.CatalogEntry, error) {
e, found, err := c.Catalog.Entry(module)
if err != nil {
return catalog.CatalogEntry{}, fmt.Errorf("the catalog half's entry of %s cannot be read: %w", module, err)
}
if !found {
return catalog.CatalogEntry{}, fmt.Errorf("the catalog half holds no entry of %s, so no manifest of it is known", module)
}
return e, nil
}
// builtDeclarations is a module's declarations at a build: the manifest of the catalog entry of its fingerprint (design 47
// G §594, §614); an entry still asked, or the seat's word that it holds none, is an error naming it.
func (c *Config) builtDeclarations(module, fp string) (ModuleDeclarations, error) {
key, err := builds.BuilderBuildKey(module, fp)
if err != nil {
return ModuleDeclarations{}, err
e, state := c.Catalog.ByFingerprint(context.Background(), fp)
switch state {
case catalogclient.EntryAnswered:
if e.Module != module {
return ModuleDeclarations{}, fmt.Errorf("the catalog entry of %s is %s's, not %s's", short(fp), e.Module, module)
}
return c.entryDeclarations(e)
case catalogclient.EntryAsked:
return ModuleDeclarations{}, fmt.Errorf("the catalog entry of build %s of %s is asked of the seat mesh-catalog and not answered yet", short(fp), module)
}
e, err := c.Store.Get(BuildsRead, key)
if err != nil {
return ModuleDeclarations{}, fmt.Errorf("the builder's %s cannot be read: %w", key, err)
}
if e == nil {
return ModuleDeclarations{}, fmt.Errorf("the builder holds no build %s", key)
}
var b struct {
Manifest string `json:"manifest"`
}
if err := json.Unmarshal(e.Value, &b); err != nil || b.Manifest == "" {
return ModuleDeclarations{}, fmt.Errorf("the builder's %s names no manifest", key)
}
return c.manifestDeclarations(module, b.Manifest)
return ModuleDeclarations{}, fmt.Errorf("the seat mesh-catalog holds no entry of build %s of %s", short(fp), module)
}
// manifestDeclarations is the declarations of the manifest of a digest in the indexer's manifests, read once: a
// manifests key is written once and never changes (ADR 0429 §16), so what it declares is kept by digest.
func (c *Config) manifestDeclarations(module, digest string) (ModuleDeclarations, error) {
c.manifestsMu.Lock()
d, ok := c.manifests[digest]
c.manifestsMu.Unlock()
if ok {
return d, nil
// entryDeclarations is the declarations of an entry's manifest, kept by the manifest's digest where it has one: a manifest
// by its digest never changes, so what it declares is read once.
func (c *Config) entryDeclarations(e catalog.CatalogEntry) (ModuleDeclarations, error) {
digest := e.ManifestDigest
if digest != "" {
c.manifestsMu.Lock()
d, ok := c.manifests[digest]
c.manifestsMu.Unlock()
if ok {
return d, nil
}
}
key, err := catalogclient.ManifestsKey(digest)
raw, err := json.Marshal(e.Manifest)
if err != nil || len(e.Manifest) == 0 {
return ModuleDeclarations{}, fmt.Errorf("the catalog entry of %s carries no manifest", e.Module)
}
d, err := ManifestDeclarations(raw)
if err != nil {
return ModuleDeclarations{}, err
return ModuleDeclarations{}, fmt.Errorf("the manifest of %s: %w", e.Module, err)
}
e, err := c.Store.Get(ManifestsRead, key)
if err != nil {
return ModuleDeclarations{}, fmt.Errorf("the indexer's manifest %s cannot be read: %w", short(digest), err)
if digest != "" {
c.manifestsMu.Lock()
if c.manifests == nil {
c.manifests = map[string]ModuleDeclarations{}
}
c.manifests[digest] = d
c.manifestsMu.Unlock()
}
if e == nil {
return ModuleDeclarations{}, fmt.Errorf("the indexer holds no manifest %s", short(digest))
}
var m struct {
Module string `json:"module"`
JSON string `json:"manifest"`
}
if err := json.Unmarshal(e.Value, &m); err != nil || m.JSON == "" {
return ModuleDeclarations{}, fmt.Errorf("the indexer's manifest %s is not one", short(digest))
}
if m.Module != module {
return ModuleDeclarations{}, fmt.Errorf("the indexer's manifest %s is %s's, not %s's", short(digest), m.Module, module)
}
d, err = ManifestDeclarations([]byte(m.JSON))
if err != nil {
return ModuleDeclarations{}, fmt.Errorf("the manifest %s: %w", short(digest), err)
}
c.manifestsMu.Lock()
if c.manifests == nil {
c.manifests = map[string]ModuleDeclarations{}
}
c.manifests[digest] = d
c.manifestsMu.Unlock()
return d, nil
}
@@ -13,7 +13,6 @@ import (
"encoding/json"
"errors"
"fmt"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"time"
)
@@ -44,8 +43,8 @@ func (c *Config) source() (Switch, error) {
// ---- declared --------------------------------------------------------------------------------------------
// DeclaredKept is one key of `declared`: a module's declarations as the manifest the indexer's `modules.<module>`
// names holds them, with that manifest's digest and commit (ADR 0565 §3).
// DeclaredKept is one key of `declared`: a module's declarations as the manifest its catalog graph entry
// carries holds them, with that manifest's digest and commit (ADR 0565 §3).
type DeclaredKept struct {
Module string `json:"module"`
Manifest string `json:"manifest"`
@@ -55,26 +54,15 @@ type DeclaredKept struct {
// trunkKept is a module's declarations at the trunk with the manifest they come from.
func (c *Config) trunkKept(module string) (DeclaredKept, error) {
key, err := catalogclient.ModulesKey(module)
e, err := c.trunkEntry(module)
if err != nil {
return DeclaredKept{}, err
}
e, err := c.Store.Get(ModulesRead, key)
if err != nil || e == nil {
return DeclaredKept{}, fmt.Errorf("the indexer's modules.%s cannot be read (%v)", module, err)
}
var m struct {
Manifest string `json:"manifest"`
Commit string `json:"commit"`
}
if err := json.Unmarshal(e.Value, &m); err != nil || m.Manifest == "" {
return DeclaredKept{}, fmt.Errorf("the indexer's modules.%s names no manifest", module)
}
d, err := c.manifestDeclarations(module, m.Manifest)
d, err := c.entryDeclarations(e)
if err != nil {
return DeclaredKept{}, err
}
return DeclaredKept{Module: module, Manifest: m.Manifest, Commit: m.Commit, Config: d.Config}, nil
return DeclaredKept{Module: module, Manifest: e.ManifestDigest, Commit: e.Commit, Config: d.Config}, nil
}
// writeDeclared makes `declared` hold each placed module's trunk declarations, writing a key only when it differs
@@ -24,8 +24,9 @@ import (
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"io"
"maps"
"os"
"os/exec"
"path/filepath"
@@ -74,6 +75,11 @@ type player struct {
subs []string
// owned is each delivery owner's state as the owner's tool answers it: its values and the revision of its last change.
owned map[string]*ownedState
// catalog is the seat mesh-catalog's trunk revision of the watched branch main, as its verb revision answers it: each
// module's entry, at the revision catRev (0: none yet); byFp is the entry of a build's fingerprint, as entry answers it.
catalog map[string]catalog.CatalogEntry
byFp map[string]catalog.CatalogEntry
catRev int64
}
// ownedState is one state of an owner (the indexer's modules, the assigner's wants...), kept from its bucket.
@@ -364,6 +370,26 @@ func (p *player) asked(key string, body json.RawMessage) any {
return a
}
switch {
case key == catalogclient.RegistrationsVerb:
return catalog.RegistrationsAnswer{Registrations: []catalog.Registration{{Module: Module, Version: 1, Branches: []string{"main"}}}}
case key == catalogclient.SourceVerb:
return delivery.SourceAnswer{}
case key == catalogclient.RevisionVerb:
if p.catRev == 0 {
return catalog.RevisionAnswer{NoneYet: true}
}
entries := make([]catalog.CatalogEntry, 0, len(p.catalog))
for _, name := range slices.Sorted(maps.Keys(p.catalog)) {
entries = append(entries, p.catalog[name])
}
return catalog.RevisionAnswer{Revision: &catalog.CatalogRevision{Revision: p.catRev, Kind: catalogclient.KindTrunk, Branch: "main"}, Entries: entries}
case key == catalogclient.EntryVerb:
var q catalog.EntryInput
_ = json.Unmarshal(body, &q)
if e, ok := p.byFp[q.Fingerprint]; ok {
return catalog.EntryAnswer{Entry: e}
}
return map[string]any{"error": "no entry of " + q.Fingerprint}
case key == HavesAsk:
var q struct{ Node, Module string }
_ = json.Unmarshal(body, &q)
@@ -543,7 +569,7 @@ func (p *player) viewsReady() {
Answered time.Time `json:"Answered"`
} `json:"first-views"`
}
if json.Unmarshal(raw, &a) != nil || len(a.Views) != len(followed) {
if json.Unmarshal(raw, &a) != nil || len(a.Views) != len(followed)+2 { // the registrations and the branch main
return false
}
for _, v := range a.Views {
@@ -565,30 +591,53 @@ func eventually(t *testing.T, what string, ok func() bool) {
t.Fatalf("never: %s", what)
}
// manifestIn writes a module's manifest into the indexer's buckets as the indexer writes it, and names it as the trunk's.
func (p *player) manifestIn(module, raw string) string {
// fpOf is a module's build at a base fingerprint: a fingerprint names one entry of the seat mesh-catalog, so two modules never
// share one.
func fpOf(module, base string) string { return base + ":" + module }
// manifestIn makes a module's manifest the trunk's: the seat mesh-catalog's next revision lists its entry, and says it as
// the event catalog-revision, as the seat does.
func (p *player) manifestIn(module, raw string) {
p.t.Helper()
digest := catalogclient.ManifestDigest([]byte(raw))
value, err := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: module, Manifest: raw})
if err != nil {
var whole map[string]any
if err := json.Unmarshal([]byte(raw), &whole); err != nil {
p.t.Fatal(err)
}
p.put("mesh-delivery-indexer.manifests", digest, json.RawMessage(value))
p.put("mesh-delivery-indexer.modules", module, map[string]any{"v": 1, "manifest": digest, "repository": "novox/" + module, "commit": "c1"})
return digest
p.mu.Lock()
p.catalog[module] = catalog.CatalogEntry{Module: module, Manifest: whole, ManifestDigest: catalogclient.ManifestDigest([]byte(raw)), Commit: "c1"}
p.catRev++
rev := p.catRev
p.mu.Unlock()
p.hearsCatalog()
// The key is the subscribed pattern itself: mesh-sdk's TopicMatches does not read `>` (reported to the lead), so only a key equal
// to it reaches the module.
p.event(catalog.Seat+"."+catalog.EventCatalogRevision+".>", catalog.CatalogRevisionEvent{Revision: rev, Kind: catalogclient.KindTrunk, Branch: "main", Changed: []string{module}})
}
// buildIn writes a build of a module with a manifest, and the want that names it on a node.
// buildIn makes the seat mesh-catalog hold the entry of a build's fingerprint, and the want that names it on a node.
func (p *player) buildIn(node, module, fp, raw string) {
p.t.Helper()
digest := catalogclient.ManifestDigest([]byte(raw))
value, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: module, Manifest: raw})
p.put("mesh-delivery-indexer.manifests", digest, json.RawMessage(value))
bk, _ := builds.BuilderBuildKey(module, fp)
p.put("mesh-delivery-builder.builds", bk, map[string]any{"v": 1, "state": "done", "manifest": digest})
var whole map[string]any
if err := json.Unmarshal([]byte(raw), &whole); err != nil {
p.t.Fatal(err)
}
p.mu.Lock()
p.byFp[fp] = catalog.CatalogEntry{Module: module, Fp: fp, Manifest: whole, ManifestDigest: catalogclient.ManifestDigest([]byte(raw))}
p.mu.Unlock()
p.put("mesh-delivery-assigner.wants", "want."+node+"."+module, map[string]any{"v": 1, "fp": fp, "self": true})
}
// hearsCatalog waits until the module subscribed to the seat's event catalog-revision: a revision said before that would
// reach it only as the first view it asks for. Waited on, never slept for.
func (p *player) hearsCatalog() {
p.t.Helper()
eventually(p.t, "the module subscribed to catalog-revision", func() bool {
p.mu.Lock()
defer p.mu.Unlock()
return slices.Contains(p.subs, catalog.Seat+"."+catalog.EventCatalogRevision+".>")
})
}
func (p *player) placed(node string, modules ...string) {
p.t.Helper()
pl := meshseat.Assignments{V: 1, Node: node}
@@ -626,7 +675,7 @@ func start(t *testing.T, bin string, js jetstream.JetStream, router *standIn, ha
stdout, _ := cmd.StdoutPipe()
cmd.Stderr = os.Stderr
p := &player{t: t, ctx: ctx, js: js, router: router, in: stdin, pending: map[string]chan wire{}, haves: haves, readers: readers,
owned: map[string]*ownedState{}}
owned: map[string]*ownedState{}, catalog: map[string]catalog.CatalogEntry{}, byFp: map[string]catalog.CatalogEntry{}}
for _, f := range followed {
p.own(f.Read)
}
@@ -658,8 +707,8 @@ func TestConfigWholeRenamesAndRetiresAgainstTheBus(t *testing.T) {
if err != nil {
t.Fatal(err)
}
buckets := []string{"mesh-delivery-assigner_assignments", "mesh-delivery-assigner_wants", "mesh-delivery-builder_builds",
"mesh-delivery-indexer_modules", "mesh-delivery-indexer_manifests", "mesh-delivery-indexer_predefined",
buckets := []string{"mesh-delivery-assigner_assignments", "mesh-delivery-assigner_wants",
"mesh-delivery-indexer_predefined",
"mesh-delivery-indexer_config-candidates"}
for _, s := range []string{ValuesState, ResolvedState, PredefinedState, ProposalsState, PullsState, KeptState, DeclaredState,
RenamesState, RetiringState, FindingsState} {
@@ -671,7 +720,7 @@ func TestConfigWholeRenamesAndRetiresAgainstTheBus(t *testing.T) {
}
}
router := newStandIn()
haves := map[string]string{"g14.player": fp1, "novox.player": fp1, "g14.radio": fp1}
haves := map[string]string{"g14.player": fpOf("player", fp1), "novox.player": fpOf("player", fp1), "g14.radio": fpOf("radio", fp1)}
p := start(t, bin, js, router, haves, nil)
p.viewsReady()
@@ -681,9 +730,9 @@ func TestConfigWholeRenamesAndRetiresAgainstTheBus(t *testing.T) {
const dock = `{"module":"dock","config":{"a":{"type":"number","default":1},"b":{"type":"number","default":1,"renamed-from":"a"}}}`
p.manifestIn("player", oldPlayer)
p.manifestIn("radio", oldRadio)
p.buildIn("g14", "player", fp1, oldPlayer)
p.buildIn("novox", "player", fp1, oldPlayer)
p.buildIn("g14", "radio", fp1, oldRadio)
p.buildIn("g14", "player", fpOf("player", fp1), oldPlayer)
p.buildIn("novox", "player", fpOf("player", fp1), oldPlayer)
p.buildIn("g14", "radio", fpOf("radio", fp1), oldRadio)
p.placed("g14", "player", "radio")
p.placed("novox", "player")
if _, err := p.call(ResyncTool, nil); err != nil {
@@ -720,7 +769,7 @@ func TestConfigWholeRenamesAndRetiresAgainstTheBus(t *testing.T) {
// A refused move writes its finding: dock declares both names.
p.manifestIn("dock", dock)
p.buildIn("g14", "dock", fp1, dock)
p.buildIn("g14", "dock", fpOf("dock", fp1), dock)
p.placed("g14", "player", "radio", "dock")
eventually(t, "the refused rename's finding", func() bool {
keys, _ := (func() ([]string, error) { kv, _ := p.kv(FindingsState); return kv.Keys(p.ctx) })()
@@ -768,9 +817,9 @@ func TestConfigWholeRenamesAndRetiresAgainstTheBus(t *testing.T) {
}
// g14 applies the build that reads the new name; the delivery says so; its window closes, novox's stays.
p2.buildIn("g14", "player", fp2, newPlayer)
p2.buildIn("g14", "player", fpOf("player", fp2), newPlayer)
p2.mu.Lock()
p2.haves["g14.player"] = fp2
p2.haves["g14.player"] = fpOf("player", fp2)
p2.mu.Unlock()
p2.event("mesh-delivery-view.delivered", map[string]any{"revision": "r1"})
eventually(t, "g14's window closes and the old name goes", func() bool {
@@ -792,9 +841,9 @@ func TestConfigWholeRenamesAndRetiresAgainstTheBus(t *testing.T) {
if len(router.asks) != 0 || p2.publishedCount(EventRetiring) != 0 {
t.Fatalf("asked %d times, retiring said %d times while g14 reads the value", len(router.asks), p2.publishedCount(EventRetiring))
}
p2.buildIn("g14", "radio", fp2, newRadio)
p2.buildIn("g14", "radio", fpOf("radio", fp2), newRadio)
p2.mu.Lock()
p2.haves["g14.radio"] = fp2
p2.haves["g14.radio"] = fpOf("radio", fp2)
p2.mu.Unlock()
p2.event("mesh-delivery-view.delivered", map[string]any{"revision": "r2"})
eventually(t, "the operator is asked once", func() bool {
-1
View File
@@ -16,7 +16,6 @@ require (
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
github.com/nats-io/nats.go v1.54.0
)
+8 -11
View File
@@ -1,9 +1,6 @@
{
"module": "mesh-config",
"version": "1",
"packages": {
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds": "0.1"
},
"runs-as": "mesh-config",
"claims": [
{
@@ -28,12 +25,13 @@
"mesh-delivery-view.delivered",
"node-engine.manifest-reads-changed",
"mesh-catalog.manifest-accepted",
"mesh-delivery-indexer.views.modules",
"mesh-delivery-indexer.views.manifests",
"mesh-delivery-indexer.views.predefined",
"mesh-delivery-indexer.views.config-candidates",
"mesh-delivery-assigner.views.assignments",
"mesh-delivery-assigner.views.wants",
"mesh-catalog.catalog-revision",
"mesh-catalog.registration-changed",
"mesh-delivery.source-moved",
"mesh-conditions.findings-asked"
],
"invokes": [
@@ -44,15 +42,14 @@
"seat:node-engine.manifest-reads",
"mesh-delivery-indexer.indexer_config_readers",
"seat:mesh-vault.retire",
"mesh-delivery-indexer.indexer_read_modules",
"mesh-delivery-indexer.indexer_read_manifests",
"mesh-delivery-indexer.indexer_read_predefined",
"mesh-delivery-indexer.indexer_read_config_candidates",
"mesh-delivery-assigner.assigner_read_assignments",
"mesh-delivery-assigner.assigner_read_wants"
],
"reads": [
"mesh-delivery-builder.builds"
"mesh-delivery-assigner.assigner_read_wants",
"seat:mesh-catalog.revision",
"seat:mesh-catalog.registrations",
"seat:mesh-catalog.entry",
"seat:mesh-delivery.source"
],
"tools": [
"config_declared",