From 48f5a7d3a99a54756175aae9bfbdb3295e439b72 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 18:18:22 +0200 Subject: [PATCH] module check refuses an artifact context naming another repository (issue 1019) --- cmd/mesh-controller/check.go | 15 +++++++++++++ cmd/mesh-controller/check_test.go | 37 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/cmd/mesh-controller/check.go b/cmd/mesh-controller/check.go index 0aeebb18..ce08029d 100644 --- a/cmd/mesh-controller/check.go +++ b/cmd/mesh-controller/check.go @@ -11,6 +11,7 @@ import ( "time" "github.com/novox/mesh-controller/internal/catalogue" + "github.com/novox/mesh-controller/internal/link" ) // moduleCheck judges manifests where they are written, with no mesh (novox/hq ADR 0037, issue 148). @@ -114,6 +115,20 @@ func moduleCheckFor(paths []string, longestMachine int, out io.Writer) error { failed += len(wrong) faulted[m.Module] = true } + // A module lives whole in one repository (novox/hq ADR 0420 §1, issue 1019): when the check is told the + // repository it runs for (MESH_CHECK_REPOSITORY, set by the build's check), an artifact building from + // another is refused, as the merge gate refuses it. A run by hand names no repository and judges none. + if repo := os.Getenv("MESH_CHECK_REPOSITORY"); repo != "" && m.Build != nil { + for _, a := range m.Build.Artifacts { + if a.Context != nil && a.Context.Repository != "" && !sameRepository(a.Context.Repository, + link.SourceMoved{Owner: ownerOf(repo), Repo: repoOf(repo)}) { + fmt.Fprintf(out, "%s: %s builds artifact %s from %s: a module lives whole in one repository "+ + "(novox/hq ADR 0420 §1)\n", path, m.Module, a.Name, a.Context.Repository) + failed++ + faulted[m.Module] = true + } + } + } if named := catalogue.InstallationProblems(m); len(named) > 0 { for _, p := range named { fmt.Fprintf(out, "%s: %s\n", path, p) diff --git a/cmd/mesh-controller/check_test.go b/cmd/mesh-controller/check_test.go index 210fe9f5..eecf6139 100644 --- a/cmd/mesh-controller/check_test.go +++ b/cmd/mesh-controller/check_test.go @@ -122,3 +122,40 @@ func TestASeatAnotherModuleDeclaresIsANoteOverSomeManifests(t *testing.T) { t.Fatalf("the note was not said:\n%s", out.String()) } } + +// **A module lives whole in one repository** (novox/hq ADR 0420 §1, issue 1019): `module check`, told the +// repository it runs for, refuses an artifact whose build context names another; run by hand, it does not know. +func TestModuleCheckRefusesAnArtifactContextNamingAnotherRepository(t *testing.T) { + dir := t.TempDir() + write := func(name, context string) string { + path := filepath.Join(dir, name+".json") + raw := `{"module":"` + name + `","version":"1","build":{"artifacts":[{"name":"server","kind":"image","from":"Dockerfile"` + context + `}]}}` + if err := os.WriteFile(path, []byte(raw), 0o600); err != nil { + t.Fatal(err) + } + return path + } + other := write("other", `,"context":{"seat":"git","repository":"novox/mesh-controller","ref":"main"}`) + own := write("own", `,"context":{"seat":"git","repository":"novox/mesh-catalog","ref":"main"}`) + none := write("none", ``) + + t.Setenv("MESH_CHECK_REPOSITORY", "novox/mesh-catalog") + var out bytes.Buffer + if err := moduleCheck([]string{other}, &out); err == nil { + t.Fatalf("a context naming another repository passed:\n%s", out.String()) + } + if !strings.Contains(out.String(), "other builds artifact server from novox/mesh-controller: a module lives whole in one repository (novox/hq ADR 0420 §1)") { + t.Errorf("the refusal is not in the words of the gate:\n%s", out.String()) + } + for _, path := range []string{own, none} { + out.Reset() + if err := moduleCheck([]string{path}, &out); err != nil { + t.Errorf("%s was refused: %v\n%s", path, err, out.String()) + } + } + t.Setenv("MESH_CHECK_REPOSITORY", "") + out.Reset() + if err := moduleCheck([]string{other}, &out); err != nil { + t.Errorf("a local run, with no repository named, refused it: %v\n%s", err, out.String()) + } +}