diff --git a/modules/mesh-delivery-indexer/internal/index/catalog_edges.go b/modules/mesh-delivery-indexer/internal/index/catalog_edges.go index 1ede7055..e9b00c19 100644 --- a/modules/mesh-delivery-indexer/internal/index/catalog_edges.go +++ b/modules/mesh-delivery-indexer/internal/index/catalog_edges.go @@ -2,11 +2,13 @@ package index import ( "cmp" + "encoding/json" "maps" "slices" "strings" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-sdk/go/catalogue" ) // The edges of a catalog graph entry (design 47 G §47 to §50, §121; A-208): the indexer writes, in each entry of a revision, @@ -116,13 +118,19 @@ func edgesOf(a meshcatalog.CatalogEntry, all map[string]meshcatalog.CatalogEntry } } // Kind 6: a verb or a tool it invokes and another entry serves (§47), unless its edge lies on a cycle and is left out (ADR 0614 §3). + // A verb whose answer's fields it relies on (the long form of `invokes`, §558) names them in the edge's `why`. + fields := manifestOf(a).InvokeFields for _, verb := range a.Invokes { if slices.Contains(a.LeftOut, verb) { continue } + why := verb + if named := fields[verb]; len(named) > 0 { + why += " fields " + strings.Join(named, ", ") + } for _, m := range modules { if b := all[m]; m != a.Module && slices.Contains(b.Serves, verb) { - add(meshcatalog.Edge{Kind: 6, From: primaryOf(b), To: delivers, Word: verb, Why: verb}) + add(meshcatalog.Edge{Kind: 6, From: primaryOf(b), To: delivers, Word: verb, Why: why}) } } } @@ -132,6 +140,19 @@ func edgesOf(a meshcatalog.CatalogEntry, all map[string]meshcatalog.CatalogEntry return out } +// manifestOf is the manifest the entry carries whole, read by the manifest reader; none where it cannot be read. +func manifestOf(e meshcatalog.CatalogEntry) catalogue.Manifest { + raw, err := json.Marshal(e.Manifest) + if err != nil { + return catalogue.Manifest{} + } + m, err := catalogue.Decode(raw) + if err != nil { + return catalogue.Manifest{} + } + return m +} + type need struct{ by, ref string } // needsOf are the artifact references each artifact of the entry's manifest names in its `needs`, with the artifact that names them. diff --git a/modules/mesh-delivery-indexer/internal/index/catalog_edges_test.go b/modules/mesh-delivery-indexer/internal/index/catalog_edges_test.go index 99116dd1..8beaa9d5 100644 --- a/modules/mesh-delivery-indexer/internal/index/catalog_edges_test.go +++ b/modules/mesh-delivery-indexer/internal/index/catalog_edges_test.go @@ -64,3 +64,17 @@ func TestAKeptRevisionHoldsTheEdgesOfItsEntries(t *testing.T) { } } } + +// Design 47 G §558, A-208: a verb invoked in the long form {verb, fields} gives its kind-6 edge the fields it relies on, +// read by the second reader release; the short form names none. +func TestAKindSixEdgeNamesTheFieldsALongFormInvokeRelies(t *testing.T) { + entries := map[string]meshcatalog.CatalogEntry{ + "holder": {Module: "holder", Artifacts: []meshcatalog.Artifact{{Ref: "holder:p"}}, Serves: []string{"seat:s.list", "seat:s.get"}}, + "user": {Module: "user", Artifacts: []meshcatalog.Artifact{{Ref: "user:p"}}, Invokes: []string{"seat:s.get", "seat:s.list"}, + Manifest: map[string]any{"module": "user", "version": "1.0", "invokes": []any{"seat:s.get", map[string]any{"verb": "seat:s.list", "fields": []any{"binding", "node"}}}}}, + } + got := WithEdges(entries)["user"].Edges + if len(got) != 2 || got[0].Why != "seat:s.get" || got[1].Why != "seat:s.list fields binding, node" || got[1].Word != "seat:s.list" { + t.Errorf("edges %+v, want the short verb as it is and the long form with its fields", got) + } +}