Registry milestone: mesh-sdk (issue 1966) #167

Merged
mesh-tools merged 149 commits from feat/registry-milestone into main 2026-11-07 14:18:44 +00:00
4 changed files with 75 additions and 7 deletions
Showing only changes of commit 2e4eb0be90 - Show all commits
+35 -6
View File
@@ -163,12 +163,8 @@ func ManifestNeeds(raw []byte) ([]string, error) {
return nil, err
}
used := map[string]bool{}
for _, f := range referenceForms {
if bytes.Contains(raw, []byte("${"+f+":")) {
used[ReadForm+f] = true
}
}
if ofSeatReach.Match(raw) || claimsAtMesh(top, ContainerImageRegistrySeat) {
textNeeds(raw, used)
if claimsAtMesh(top, ContainerImageRegistrySeat) {
used[ReadForm+ReferenceFormSeatReach] = true
}
if _, has := top[ConfigField]; has || takesConfigKey.Match(raw) {
@@ -429,6 +425,39 @@ func asList(v any) []any {
return list
}
// textNeeds adds the reference forms a manifest's or a template's text writes to used: a form the engine fills in a
// file's text is read there whether the text is inline or in a template file.
func textNeeds(text []byte, used map[string]bool) {
for _, f := range referenceForms {
if bytes.Contains(text, []byte("${"+f+":")) {
used[ReadForm+f] = true
}
}
if ofSeatReach.Match(text) {
used[ReadForm+ReferenceFormSeatReach] = true
}
}
// ModuleNeeds is ManifestNeeds of a module whose files take their text from templates (template.go): what the manifest
// needs and what each template's text writes, by template path (TemplatePaths), sorted. A module's needs are both: an
// engine renders the template's text as it renders inline content, so a template naming ${seat:x:reach} needs the
// reading as the same content inline does, though the manifest itself is unchanged (novox/hq ADR 0632 §2). A template
// named and not given is no need; the reader of the files says it could not read one.
func ModuleNeeds(raw []byte, templates map[string]string) ([]string, error) {
needs, err := ManifestNeeds(raw)
if err != nil || len(templates) == 0 {
return needs, err
}
used := map[string]bool{}
for _, n := range needs {
used[n] = true
}
for _, text := range templates {
textNeeds([]byte(text), used)
}
return sortedSet(used), nil
}
func sortedSet(set map[string]bool) []string {
out := make([]string, 0, len(set))
for k := range set {
+16 -1
View File
@@ -1,6 +1,7 @@
package catalogue
import (
"maps"
"os"
"slices"
"testing"
@@ -87,12 +88,26 @@ func TestOwnManifestUsesNoWordTheRunningControllerRefuses(t *testing.T) {
{"the store's holder", `{"module":"distribution","version":"1","claims":[{"name":"container-image-registry",` +
`"scope":"mesh","serves":["list"]}],"listens":[{"name":"registry","port":5000,"from":"mesh"}]}`,
[]string{"form:seat-reach", "seat:container-image-registry"}, []string{"form:seat-reach", "seat:container-image-registry"}},
// docker's daemon file as ADR 0632 leaves it: the manifest unchanged, its template naming the image seat's reach;
// TemplatePaths names the template a reader of the files reads.
{"a template naming a seat's reach", `{"module":"d","version":"1","resources":[{"id":"daemon","type":"file",` +
`"path":"/etc/d.json","template":"files/daemon.json"}]}`, []string{"form:seat", "form:seat-reach"},
[]string{"form:seat-reach"}},
{"a word this contract does not know", `{"module":"u","version":"1","newer-word":{"inside":1}}`, []string{"word:module"}, nil},
} {
needs, err := ManifestNeeds([]byte(row.raw))
templates := map[string]map[string]string{ // the templates' text a row's module names
"a template naming a seat's reach": {"files/daemon.json": `{"x":"${seat:container-image-registry:reach}"}`},
}[row.why]
needs, err := ModuleNeeds([]byte(row.raw), templates)
if err != nil {
t.Fatalf("%s: %v", row.why, err)
}
if templates != nil {
m, err := Decode([]byte(row.raw))
if paths := TemplatePaths(m); err != nil || !slices.Equal(paths, slices.Sorted(maps.Keys(templates))) {
t.Errorf("%s: TemplatePaths = %v (%v), want %v", row.why, paths, err, slices.Sorted(maps.Keys(templates)))
}
}
for _, n := range row.needs {
if !slices.Contains(needs, n) {
t.Errorf("%s: needs %v, not %s", row.why, needs, n)
+20
View File
@@ -8,6 +8,7 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
"unicode/utf8"
)
@@ -184,6 +185,25 @@ func fillItemTemplates(m Manifest, items []Item, text func(string) (string, bool
return out, nil
}
// TemplatePaths is every template the manifest's files and items name, by its path in the module's directory, sorted
// and once each: what a reader of the module's files reads for ModuleNeeds. A template whose name is wrong is left out;
// the judge refuses it by name.
func TemplatePaths(m Manifest) []string {
var out []string
for _, r := range m.Resources {
if name, has, wrong := templateOf(m.Module, m.TemplatesDir(), r); has && wrong == "" {
out = append(out, name)
}
}
for _, it := range m.Items {
if it.Template != "" && itemTemplateProblem(m, it.Template) == "" {
out = append(out, it.Template)
}
}
slices.Sort(out)
return slices.Compact(out)
}
// ReadTemplates is the text of each template the manifest names, read from the module's directory in a repository tree,
// by template path. Refused by name: a template the directory lacks, and anything under its templates directory
// (TemplatesDir) the build's packing refuses — that directory itself, or any directory or file under it, that is a
+4
View File
@@ -594,6 +594,10 @@ type Forge struct {
// relative to it (novox/hq ADR 0465 clause 1). Older writers send neither.
Manifests map[string]string `json:"manifests,omitempty"`
Locks map[string]map[string]string `json:"locks,omitempty"`
// Needs are, of each such directory whose manifest names templates, the reader capabilities of the module whole:
// its manifest's and its templates' text's at the same commit (catalogue.ModuleNeeds; novox/hq ADR 0632 §2). The
// indexer takes them over the manifest's own; a directory absent, or an older writer, has the manifest's alone.
Needs map[string][]string `json:"needs,omitempty"`
// Paths are, of each module directory whose manifest names paths on every artifact, each named path's git object
// id at the same commit as its manifest, by path relative to the directory (novox/hq ADR 0499 clause 3). The
// indexer names the paths; a message without them gives such a module none. Older writers send none.