Merge pull request 'G start: the indexer serves modules again, the watchman builds, the graph's order comes from the verbs a package calls' (#216) from fix/47-G-start into main

Reviewed-on: #216
This commit was merged in pull request #216.
This commit is contained in:
2026-11-13 19:08:09 +00:00
10 changed files with 267 additions and 20 deletions
@@ -466,7 +466,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
SeatDefinitions: awaitedStore{b: seatDefs}, ReadFile: readFileVia(calls.Call), SaySeatDefinition: saySeats,
Revisions: revisions, EntryIndex: awaitedStore{b: process.AwaitBucket(ctx, js, conn, index.Bucket(cred.Module, index.EntryIndexState), "no entry is found by its fingerprint", logger.Printf)}, // Message a kept call: a read, called again through a transport fault (ADR 0668 §1)
// Each catalog revision may bring another holder of a build seat: the build modules are found again (buildmodules.go).
SayCatalogRevision: afterRevision(refreshingBuildModules(sayCatalogRevision(conn, cred), &buildFollow), &fillFollow),
// A new trunk revision that changes the modules is said on the views family of modules too (design 47 G A-360).
SayCatalogRevision: afterRevision(refreshingBuildModules(sayingModulesView(sayCatalogRevision(conn, cred), cred, views.JetStreamPublish(js)), &buildFollow), &fillFollow),
SayFill: sayFill(conn, cred),
// The catalog's released versions (design 47 G §349): the state versions, bound when it appears, and the event version-released.
Versions: awaitedStore{b: process.AwaitBucket(ctx, js, conn, index.Bucket(cred.Module, index.VersionsState), "no released version is kept, so no package-published makes a version", logger.Printf)},
@@ -0,0 +1,56 @@
package main
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/keys"
"git.novox.be/novox/mesh-sdk/go/views"
"mesh-delivery-indexer/internal/index"
)
// Design 47 G A-360 (completing A-354): a new trunk revision that changes the modules says one views event of modules,
// the mark at the revision's number, on the indexer's own event subject; a revision of another watched branch, one that
// changes nothing, and a publish that fails say so.
func TestANewTrunkRevisionChangingTheModulesSaysOneViewsEventOfModules(t *testing.T) {
at := time.Date(2026, 11, 13, 18, 0, 0, 0, time.UTC)
var sent []*nats.Msg
var ids []string
var fail error
said := 0
say := sayingModulesView(func(context.Context, meshcatalog.CatalogRevisionEvent) error { said++; return nil }, testAccount,
func(_ context.Context, m *nats.Msg, id string) error {
sent, ids = append(sent, m), append(ids, id)
return fail
})
trunk := keys.EscapeName(index.TrunkBranch)
for _, e := range []meshcatalog.CatalogRevisionEvent{
{Revision: 7, Kind: "trunk", Branch: trunk, At: &at, Changed: []string{"avahi", "nats"}},
{Revision: 8, Kind: "trunk", Branch: keys.EscapeName("feat/x"), At: &at, Changed: []string{"avahi"}},
{Revision: 9, Kind: "candidate", Branch: trunk, At: &at, Changed: []string{"avahi"}},
{Revision: 10, Kind: "trunk", Branch: trunk, At: &at},
} {
if err := say(context.Background(), e); err != nil {
t.Fatalf("revision %d: %v", e.Revision, err)
}
}
if said != 4 || len(sent) != 1 || sent[0].Subject != testAccount.EventSubject(views.EventName("modules")) {
t.Fatalf("catalog-revision said %d times, views events %d (%v), want 4 and one on views.modules", said, len(sent), sent)
}
ev, err := views.DecodeEvent(sent[0].Data)
if err != nil || !ev.Mark || ev.Owner != testIndexer || ev.State != "modules" || ev.Revision != 7 || !ev.At.Equal(at) || ids[0] != ev.ID() {
t.Errorf("the views event of modules is %+v (%v), id %q, want the mark of modules at revision 7 under its own id", ev, err, ids[0])
}
fail = errors.New("the bus refuses")
if err := say(context.Background(), meshcatalog.CatalogRevisionEvent{Revision: 11, Kind: "trunk", Branch: trunk, At: &at, Changed: []string{"nats"}}); err == nil ||
!strings.Contains(err.Error(), "views event of modules at revision 11") {
t.Errorf("a views event not said answered %v, want it said", err)
}
}
@@ -16,6 +16,7 @@ import (
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/views"
"mesh-delivery-indexer/internal/index"
)
@@ -145,6 +146,35 @@ func sayCatalogRevision(conn *nats.Conn, cred process.Account) func(context.Cont
}
}
// sayingModulesView is say, and then, for a new trunk revision that changes the modules, the indexer's views event of
// modules (index.ModulesViewsEvent; design 47 G A-360) on its own event subject under the event's duplicate id, so the
// readers of the first view modules hear a newly merged module without asking again. One not said is returned with
// say's error, which the take-in logs.
func sayingModulesView(say func(context.Context, meshcatalog.CatalogRevisionEvent) error, cred process.Account,
publish func(context.Context, *nats.Msg, string) error) func(context.Context, meshcatalog.CatalogRevisionEvent) error {
return func(ctx context.Context, e meshcatalog.CatalogRevisionEvent) error {
err := say(ctx, e)
ev, ok := index.ModulesViewsEvent(cred.Module, e)
if !ok {
return err
}
data, encErr := ev.Encode()
if encErr == nil {
msg := nats.NewMsg(cred.EventSubject(views.EventName(index.ModulesState)))
msg.Header.Set("x-event-id", ev.ID())
msg.Header.Set("x-source", cred.Module)
msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano))
msg.Header.Set("content-type", "application/json")
msg.Data = data
encErr = publish(ctx, msg, ev.ID())
}
if encErr != nil {
return errors.Join(err, fmt.Errorf("the views event of modules at revision %d is not said, its readers hold the revision before until the next: %w", e.Revision, encErr))
}
return err
}
}
// sayFill publishes the seat mesh-catalog's event filling, full or room of the catalog graph revisions under the duplicate
// id given (milestone 47/G W4.6), so one said twice is heard once.
func sayFill(conn *nats.Conn, cred process.Account) func(context.Context, string, string, meshcatalog.Fill) error {
@@ -21,6 +21,7 @@ import (
// WithEdges gives each entry of a revision its edges. The entries given are not changed. The edges of kinds 1 to 4 and 6 are
// each entry's own; those of kind 5 (§711) need the others' edges of kinds 1 to 4, so they come after.
func WithEdges(entries map[string]meshcatalog.CatalogEntry) map[string]meshcatalog.CatalogEntry {
entries = withCallsLeftOut(entries)
out := make(map[string]meshcatalog.CatalogEntry, len(entries))
for module, e := range entries {
e.Edges = edgesOf(e, entries)
@@ -42,6 +43,62 @@ func WithEdges(entries map[string]meshcatalog.CatalogEntry) map[string]meshcatal
return out
}
// withCallsLeftOut gives each entry the seat call words left out on a cycle (design 47 G A-361, A-357; ADR 0614 §3): the
// module graph's LeaveOut is run over the entries with a package's seat call words (seatCallsOf) counted among the invokes of
// every module built against it, and what it leaves out joins each entry's LeftOut, so a call word's edge on a cycle is
// left out like an invoked verb's, and so is an invoked verb's edge that a call word's edge closes a cycle with. The entries
// given are not changed; with no seat call word anywhere they are returned as they are.
func withCallsLeftOut(entries map[string]meshcatalog.CatalogEntry) map[string]meshcatalog.CatalogEntry {
g, any := graph.Graph{}, false
for module, e := range entries {
calls := seatCallsOf(e, entries)
any = any || len(calls) > 0
invokes := slices.Clone(e.Invokes)
for _, c := range calls {
if !slices.Contains(invokes, c) {
invokes = append(invokes, c)
}
}
g[module] = graph.Node{Bases: e.Bases, Awaits: e.Awaits, Provides: e.Provides, Invokes: invokes, Serves: e.Serves}
}
if !any {
return entries
}
g.LeaveOut()
out := make(map[string]meshcatalog.CatalogEntry, len(entries))
for module, e := range entries {
for _, v := range g[module].LeftOut {
if !slices.Contains(e.LeftOut, v) {
e.LeftOut = append(slices.Clone(e.LeftOut), v)
}
}
out[module] = e
}
return out
}
// seatCallsOf are the call words in the invokes form, `seat:<seat>.<verb>`, of the package artifacts entry a builds
// against (their `calls`), sorted, each once.
func seatCallsOf(a meshcatalog.CatalogEntry, all map[string]meshcatalog.CatalogEntry) []string {
var out []string
for _, name := range slices.Sorted(maps.Keys(a.Packages)) {
for _, m := range slices.Sorted(maps.Keys(all)) {
for _, art := range all[m].Artifacts {
if art.Package == nil || art.Package.Name != name {
continue
}
for _, word := range wordsOf(all[m], art.Ref, "calls") {
if _, _, isSeat := catalogue.SeatInvoke(word); isSeat && !slices.Contains(out, word) {
out = append(out, word)
}
}
}
}
}
slices.Sort(out)
return out
}
// kind5Steps are the steps of an acting rule that give an edge of kind 5 (§711): a grant or a credential, a composition, a
// placement and a verdict, each made for the dependent's new version. `build` gives kind 3, `take-in` and `validate` none.
var kind5Steps = []string{seatdef.StepGrant, seatdef.StepCompose, seatdef.StepPlace, seatdef.StepCheck, seatdef.StepPipeline}
@@ -265,6 +322,8 @@ func edgesOf(a meshcatalog.CatalogEntry, all map[string]meshcatalog.CatalogEntry
}
// Kind 6 again: a package it builds against whose artifact takes `calls` (the capability words its code calls of the tool
// runner) orders it after the artifact that `states` each word, so no manifest names the tool runner's module (§605, §545).
// A call word in the invokes form, `seat:<seat>.<verb>` (a seat verb the package's code calls on the module's behalf),
// orders it after the verb's server, exactly as the same word in its `invokes` would (design 47 G A-357).
for _, name := range slices.Sorted(maps.Keys(a.Packages)) {
for _, m := range modules {
for _, art := range all[m].Artifacts {
@@ -272,6 +331,13 @@ func edgesOf(a meshcatalog.CatalogEntry, all map[string]meshcatalog.CatalogEntry
continue
}
for _, word := range wordsOf(all[m], art.Ref, "calls") {
if _, _, isSeat := catalogue.SeatInvoke(word); isSeat && !slices.Contains(a.LeftOut, word) {
for _, server := range serving.Servers(word) {
if server != a.Module {
add(meshcatalog.Edge{Kind: 6, From: primaryOf(all[server]), To: delivers, Word: word, Why: "calls of " + name})
}
}
}
for _, other := range modules {
for _, stating := range all[other].Artifacts {
if other != a.Module && slices.Contains(wordsOf(all[other], stating.Ref, "states"), word) {
@@ -86,14 +86,17 @@ func TestAKindSixEdgeNamesTheFieldsALongFormInvokeRelies(t *testing.T) {
}
// Design 47 G §605, §545, A-208: a module built against a package whose artifact takes `calls` is ordered after the artifact
// that `states` each word, by an edge of kind 6 naming the word; a module built against no such package has none.
// that `states` each word, by an edge of kind 6 naming the word; a module built against no such package has none. A-357: a
// call word in the invokes form orders it after the verb's server, as from `invokes`, and the server after none of its own.
func TestAModuleBuiltOnAPackageThatCallsTheToolRunnerIsOrderedAfterTheArtifactThatStatesTheWords(t *testing.T) {
artifactsOf := func(module string, art map[string]any) map[string]any {
return map[string]any{"module": module, "build": map[string]any{"artifacts": []any{art}}}
}
entries := map[string]meshcatalog.CatalogEntry{
"sdk": {Module: "sdk", Artifacts: []meshcatalog.Artifact{{Ref: "sdk:go", Kind: "package", Language: "go", Package: &meshcatalog.ArtifactPackage{Ecosystem: EcosystemGo, Name: "git.example/sdk/go"}}},
Manifest: artifactsOf("sdk", map[string]any{"name": "go", "calls": []any{"put-sessions", "finding-calls"}})},
Manifest: artifactsOf("sdk", map[string]any{"name": "go", "calls": []any{"put-sessions", "finding-calls", "seat:mesh-catalog.registrations"}})},
"catalog": {Module: "catalog", Artifacts: []meshcatalog.Artifact{{Ref: "catalog:program", Language: "go"}}, Serves: []string{"seat:mesh-catalog.registrations"},
Packages: map[string]string{"git.example/sdk/go": "1.0.4"}},
"runner": {Module: "runner", Artifacts: []meshcatalog.Artifact{{Ref: "runner:runtime"}},
Manifest: artifactsOf("runner", map[string]any{"name": "runtime", "states": []any{"put-sessions"}})},
"app": {Module: "app", Artifacts: []meshcatalog.Artifact{{Ref: "app:program", Language: "go"}}, Packages: map[string]string{"git.example/sdk/go": "1.0.4"}},
@@ -106,15 +109,60 @@ func TestAModuleBuiltOnAPackageThatCallsTheToolRunnerIsOrderedAfterTheArtifactTh
six = append(six, e)
}
}
want := []meshcatalog.Edge{{Kind: 6, From: "runner:runtime", To: "app:program", Word: "put-sessions", Why: "calls of git.example/sdk/go"}}
want := []meshcatalog.Edge{{Kind: 6, From: "catalog:program", To: "app:program", Word: "seat:mesh-catalog.registrations", Why: "calls of git.example/sdk/go"},
{Kind: 6, From: "runner:runtime", To: "app:program", Word: "put-sessions", Why: "calls of git.example/sdk/go"}}
if !slices.Equal(six, want) {
t.Errorf("app's edges of kind 6 are %+v, want %+v", six, want)
}
if slices.ContainsFunc(got["catalog"].Edges, func(e meshcatalog.Edge) bool { return e.From == "catalog:program" }) {
t.Errorf("the verb's server is ordered after itself: %+v", got["catalog"].Edges)
}
if len(got["other"].Edges) != 0 {
t.Errorf("other builds on no such package, its edges %+v", got["other"].Edges)
}
}
// Design 47 G A-361: a package's seat call words count among the invokes of every module built against it when the graph
// leaves out the edges on a cycle, so a call word's edge on a cycle is left out like an invoked verb's (the gate and the
// indexer: the gate invokes the indexer's verb, the indexer's SDK calls the gate's), and an edge off the cycle stays.
func TestASeatCallWordsEdgeOnACycleIsLeftOutLikeAnInvokedVerbs(t *testing.T) {
sdk := map[string]any{"module": "sdk", "build": map[string]any{"artifacts": []any{map[string]any{"name": "go",
"calls": []any{"seat:mesh-delivery.source", "seat:mesh-catalog.registrations"}}}}}
pkgs := map[string]string{"git.example/sdk/go": "1.0.4"}
entries := map[string]meshcatalog.CatalogEntry{
"sdk": {Module: "sdk", Artifacts: []meshcatalog.Artifact{{Ref: "sdk:go", Kind: "package", Language: "go", Package: &meshcatalog.ArtifactPackage{Ecosystem: EcosystemGo, Name: "git.example/sdk/go"}}},
Manifest: sdk},
"gate": {Module: "gate", Artifacts: []meshcatalog.Artifact{{Ref: "gate:program", Language: "go"}}, Serves: []string{"seat:mesh-delivery.source"},
Invokes: []string{"seat:mesh-catalog.registrations"}},
"indexer": {Module: "indexer", Artifacts: []meshcatalog.Artifact{{Ref: "indexer:program", Language: "go"}}, Serves: []string{"seat:mesh-catalog.registrations"},
Packages: pkgs},
"app": {Module: "app", Artifacts: []meshcatalog.Artifact{{Ref: "app:program", Language: "go"}}, Packages: pkgs},
}
got := WithEdges(entries)
for _, m := range []string{"gate", "indexer"} {
for _, e := range got[m].Edges {
if e.Kind == 6 {
t.Errorf("%s keeps the edge %+v, which lies on the cycle gate-indexer", m, e)
}
}
}
if !slices.Contains(got["indexer"].LeftOut, "seat:mesh-delivery.source") || !slices.Contains(got["gate"].LeftOut, "seat:mesh-catalog.registrations") {
t.Errorf("left out: indexer %v, gate %v, want each one's word on the cycle", got["indexer"].LeftOut, got["gate"].LeftOut)
}
var six []meshcatalog.ArtifactRef
for _, e := range got["app"].Edges {
if e.Kind == 6 {
six = append(six, e.From)
}
}
if !slices.Equal(six, []meshcatalog.ArtifactRef{"gate:program", "indexer:program"}) || len(got["app"].LeftOut) != 0 {
t.Errorf("app's edges of kind 6 come from %v, left out %v, want both servers and none left out", six, got["app"].LeftOut)
}
if len(entries["gate"].LeftOut) != 0 {
t.Error("the entries given were changed")
}
}
func actingEntries() map[string]meshcatalog.CatalogEntry {
rule := func(seat, step, on string) meshcatalog.ActingRule {
return meshcatalog.ActingRule{Seat: seat, Step: step, On: on}
@@ -594,6 +594,17 @@ func TestBasesAreComputedFirstAndNoFingerprintTakesTheBuilder(t *testing.T) { //
if keys, err := trunkStates.State("modules").Keys(); err != nil || len(keys) != len(builderModules(t)) {
t.Errorf("the bundle's modules = %v (%v), want every module of revision 2", keys, err)
}
// Design 47 G A-354: the first view modules answers the grants part every module of the newest trunk revision in the
// form it reads (meshcatalogclient's value, naming the manifest), at that revision.
if !slices.Contains(FirstViewTools, ViewedTool{Tool: "modules", State: ModulesState, Words: "every module the indexer holds, its fingerprint, inputs and manifest digest."}) {
t.Errorf("the first-view tools %+v serve no modules", FirstViewTools)
}
if a, err := views.FirstView(testSelf, ModulesState, trunkStates.State(ModulesState), trunkStates.State(views.MarksState), "", time.Now()); err != nil ||
a.Revision != 2 || len(a.Values) != len(builderModules(t)) {
t.Errorf("the first view modules answered %d values at %d (%v), want every module at revision 2", len(a.Values), a.Revision, err)
} else if m, err := meshcatalogclient.DecodeModule(a.Values[viewKey]); err != nil || m.Manifest == "" {
t.Errorf("the first view's avahi = %s (%v), want the module's value naming its manifest", a.Values[viewKey], err)
}
if e, ok, err := trunkStates.State(views.MarksState).Get("graph"); err != nil || !ok || !strings.Contains(string(e.Value), `"revision":2`) {
t.Errorf("the bundle's mark of graph = %+v %v (%v), want revision 2", e, ok, err)
}
@@ -1,15 +1,22 @@
package index
import (
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/graph"
"git.novox.be/novox/mesh-sdk/go/keys"
"git.novox.be/novox/mesh-sdk/go/views"
)
// FirstViewTools are the indexer's first-view tools (ADR 0621 §1, ADR 0622 §1, ADR 0631 §1): the tool, the state it answers, and
// the words of what that state holds. Each answers only its callers list (CallersEnv), which the manifest writes. The fourth
// first view, of which modules exist, is the seat mesh-catalog's verb revision (ADR 0663 §1, RevisionTool), granted by
// declaration, not by a list.
// the words of what that state holds. Each answers only its callers list (CallersEnv), which the manifest writes. Which
// modules exist is the seat mesh-catalog's verb revision (ADR 0663 §1, RevisionTool), granted by declaration, not by a
// list; the first view modules stays beside it, in its form before milestone G and answered from the newest trunk
// revision (TrunkStates), until the grants part reads the catalog half (design 47 G A-354).
var FirstViewTools = []ViewedTool{
{Tool: "manifests", State: "manifests", Words: "every manifest the indexer keeps, by its digest."},
{Tool: "modules", State: ModulesState, Words: "every module the indexer holds, its fingerprint, inputs and manifest digest."},
{Tool: "verdicts", State: VerdictsState, Words: "the judge's verdict on each manifest: accepted, waiting or refused."},
{Tool: "graph", State: graph.State, Words: "the module graph: each module's build, provision and verb edges, whose verb edges a want waits on."},
}
@@ -39,3 +46,19 @@ var ViewedStates = map[string]bool{
ManifestsState: true, VerdictsState: true, PredefinedState: true, ConfigCandidatesState: true, CandidatesState: true,
PackagesState: true, HeardState: true, TakenState: true, FindingsState: true, SeatDefinitionsState: true, UnjudgedState: true,
}
// ModulesViewsEvent is what the indexer says on its views family of modules when a new revision of the trunk branch
// changes the modules (design 47 G A-360, completing A-354): the first view modules answers every value at the trunk
// revision's number, so one revision's changes are said as one event, the mark at that number, after which a reader
// whose picture is short of it asks the first view once and holds the new revision whole. A revision of another watched
// branch, or one that changes nothing, says nothing (false).
func ModulesViewsEvent(owner string, e meshcatalog.CatalogRevisionEvent) (views.Event, bool) {
if e.Kind != "trunk" || e.Branch != keys.EscapeName(TrunkBranch) || e.Revision <= 0 || len(e.Changed) == 0 {
return views.Event{}, false
}
var at time.Time
if e.At != nil {
at = e.At.UTC()
}
return views.Event{V: views.Version, Owner: owner, State: ModulesState, Revision: uint64(e.Revision), At: at, Mark: true}, true
}
+13 -13
View File
@@ -187,8 +187,8 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
// reads the verdicts state (indexer_verdicts) and asks the indexer to judge again through the takeins state
// (indexer_judge; issue 774, clause 3). 1966/AC11: indexer_old_sections reads the count of the old sections (state oldsections).
// ADR 0621 §1, ADR 0622 §1, ADR 0631 §1, ADR 0663 §1: the first-view tools manifests, verdicts and graph (modules is the seat
// verb revision's, served by the claim), and design 25 § 12.2's read
// ADR 0621 §1, ADR 0622 §1, ADR 0631 §1, ADR 0663 §1: the first-view tools manifests, modules, verdicts and graph (modules
// kept for the grants part until it reads the catalog half, design 47 G A-354), and design 25 § 12.2's read
// tools, all of the tools bundle, which runs in a runtime of its own on the indexer's account (runs-as).
want := []string{"indexer_take_in", "indexer_old_sections", "indexer_verdicts", "indexer_judge"}
for _, v := range append(append([]index.ViewedTool{}, index.FirstViewTools...), index.ReadTools...) {
@@ -206,7 +206,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
index.EntryIndexTool, index.NamedDropTool, // design 47 G §675
index.ProposalsTool, index.ProposalDropTool, index.SeedAgainTool, index.InputsTool, index.PinsTool, index.UnpinTool, index.ConsumerTool, index.RunsTool, index.RunDropTool, // design 47 G §667, §8, §579, §64 (A-194)
index.ValidateAgainTool) // design 47 G §713
if !reflect.DeepEqual(m.Tools, want) || len(want) != 50 || slices.Contains(m.Tools, "modules") {
if !reflect.DeepEqual(m.Tools, want) || len(want) != 51 || !slices.Contains(m.Tools, "modules") {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
// Design 47 G §2, §9: every version of every registration is kept with no age limit, 64 MiB, on the old streams section.
@@ -219,8 +219,8 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
if !regexp.MustCompile(`^[0-9]+\.[0-9]+$`).MatchString(m.Version) {
t.Errorf("version = %q, want MAJOR.MINOR (design 47 G §368)", m.Version)
}
// Design 47 G §196, §185 (test 61): no state modules, no tool or view of it, and no mesh-wide hold with its states, tools
// and the engines' reads it weighed.
// Design 47 G §196, §185 (test 61): no state modules, no read tool or view of it, and no mesh-wide hold with its states,
// tools and the engines' reads it weighed; its first view and its views family stay for the grants part (A-354, A-360).
for _, s := range m.State {
if slices.Contains([]string{"modules", "holds", "engine-reads", "hold-bounds"}, s.Name) {
t.Errorf("the indexer declares the state %s, which design 47 G retires", s.Name)
@@ -231,8 +231,8 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
t.Errorf("the indexer serves %s, which design 47 G retires", retired)
}
}
if slices.Contains(m.Emits, "views.modules") || slices.Contains(m.Consumes, "node-engine.manifest-reads-changed") {
t.Errorf("the indexer says views.modules or hears manifest-reads-changed: emits %v, consumes %v", m.Emits, m.Consumes)
if slices.Contains(m.Consumes, "node-engine.manifest-reads-changed") {
t.Errorf("the indexer hears manifest-reads-changed: consumes %v", m.Consumes)
}
// Every read tool of the indexer a reader of this repository invokes is one it serves (the checker's seventh first
// view, indexer_read_seat_definitions, was invoked and not served).
@@ -258,9 +258,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
t.Fatalf("%s: %v", p, err)
}
for _, inv := range invoked {
// The grants part (W8) and the assigner read modules until their own subtasks move them to revision; the join removes this line.
if (p == "../mesh-grants/module.json" || p == "../mesh-delivery-assigner/module.json") && inv == "mesh-delivery-indexer.modules" ||
inv == "mesh-delivery-indexer.indexer_read_modules" { // design 47 G §196: the readers' own subtasks move them to the catalog half
if inv == "mesh-delivery-indexer.indexer_read_modules" { // design 47 G §196: the readers' own subtasks move them to the catalog half
continue
}
if tool, ok := strings.CutPrefix(inv, "mesh-delivery-indexer."); ok && !slices.Contains(m.Tools, tool) {
@@ -281,14 +279,15 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
// ADR 0621 §2, ADR 0622 §1, ADR 0631 §1: each first-view tool answers exactly its readers; the indexer is on none, and
// graph answers the assigner alone.
// ADR 0663 §2: revision has no callers list; it answers the parts whose entry declares it.
callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants",
// Design 47 G A-354: modules answers its readers as before milestone G.
callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "modules": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants",
"graph": "mesh-delivery-assigner"}
for _, a := range m.Build.Artifacts {
if a.Name != "tools" {
continue
}
if len(a.Env) != len(callers) {
t.Errorf("tools env = %v, want only the three callers lists", a.Env)
t.Errorf("tools env = %v, want only the four callers lists", a.Env)
}
for tool, list := range callers {
if got := a.Env[views.CallersEnv(tool)]; got != list {
@@ -310,7 +309,8 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
// registrations' history (design 47 G §2, asserted above).
// ADR 0636 §4: seat-definition-changed and -removed are the seat mesh-catalog's events, said on the seat's subject and kept
// on the shared events stream, and not among its own emits.
for _, state := range []string{"candidates", "config-candidates", "findings", "graph", "heard", "manifests", "packages", "predefined", "seat-definitions", "taken", "verdicts"} {
// Design 47 G A-360: modules too, at each new trunk revision that changes it.
for _, state := range []string{"candidates", "config-candidates", "findings", "graph", "heard", "manifests", "modules", "packages", "predefined", "seat-definitions", "taken", "verdicts"} {
if !slices.Contains(m.Emits, views.EventPrefix+state) {
t.Errorf("emits = %v: want %s%s", m.Emits, views.EventPrefix, state)
}
@@ -32,6 +32,7 @@
"indexer_verdicts",
"indexer_judge",
"manifests",
"modules",
"verdicts",
"graph",
"indexer_read_manifests",
@@ -150,6 +151,7 @@
"views.graph",
"views.heard",
"views.manifests",
"views.modules",
"views.packages",
"views.predefined",
"views.seat-definitions",
@@ -244,6 +246,7 @@
],
"env": {
"MESH_VIEW_CALLERS_MANIFESTS": "mesh-grants,node-tools,mesh-delivery-assigner",
"MESH_VIEW_CALLERS_MODULES": "mesh-grants,node-tools,mesh-delivery-assigner",
"MESH_VIEW_CALLERS_VERDICTS": "mesh-grants",
"MESH_VIEW_CALLERS_GRAPH": "mesh-delivery-assigner"
}
+9
View File
@@ -58,3 +58,12 @@ require (
require git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo => ../mesh-delivery-contracts/go/seats/nodebuildgo
require (
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildimage v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildnode v0.0.0-00010101000000-000000000000
)
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildimage => ../mesh-delivery-contracts/go/seats/nodebuildimage
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildnode => ../mesh-delivery-contracts/go/seats/nodebuildnode