Stop judging manifests at registration; the controller registers what the indexer accepted
Issue 1585, S18 (ADR 0578 §3): registerJudged no longer re-judges the manifest or its predefined keys, and a standing build is read leniently; module add decodes and judges nothing. Resolve never re-parsed a manifest, so a key-key fallback needs no list there; a table row holds that. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -5,10 +5,11 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
@@ -127,17 +128,13 @@ type takenBuild struct {
|
||||
carried func() error
|
||||
}
|
||||
|
||||
// registerJudged judges a recorded build and registers its module, or says why not.
|
||||
// registerJudged registers its module, or says why not.
|
||||
func registerJudged(ctx context.Context, inv *inventory.Inventory, j takenBuild) (catalogue.Manifest, error) {
|
||||
manifest, kept := j.manifest, j.kept
|
||||
notRegistered := func(err error) (catalogue.Manifest, error) {
|
||||
return manifest, fmt.Errorf("%s built %s (%s), recorded and not registered: %w", j.by, manifest.Module,
|
||||
short(j.commit), err)
|
||||
}
|
||||
if err := namesNoInstallation(manifest); err != nil {
|
||||
return manifest, fmt.Errorf("%s built %s (%s), and the mesh does not register it: %w",
|
||||
j.by, j.what, short(j.commit), err)
|
||||
}
|
||||
// **Only from the repository the catalogue builds the module from** (novox/hq ADR 0266): else the trunk
|
||||
// below is the trunk of whatever repository was built, which may be one an agent made — and a module named
|
||||
// `sudo` from it would be what the next push sends. Another repository is the operator's, at the terminal.
|
||||
@@ -150,18 +147,12 @@ func registerJudged(ctx context.Context, inv *inventory.Inventory, j takenBuild)
|
||||
return notRegistered(err)
|
||||
}
|
||||
}
|
||||
// Then the module is judged against the list the mesh publishes, as every manifest read again is: one naming a
|
||||
// key that list does not hold, or of another type, is refused here and never enters the catalogue.
|
||||
// The manifest was judged once, by the indexer, and its verdict taken before this (buildOfKey; ADR 0578 §3).
|
||||
if j.carried != nil {
|
||||
if err := j.carried(); err != nil {
|
||||
return manifest, err
|
||||
}
|
||||
}
|
||||
if manifest.NamesPredefined() {
|
||||
if _, err := catalogue.ParseManifest(kept.Manifest); err != nil {
|
||||
return notRegistered(fmt.Errorf("judged against the list of predefined keys the mesh publishes, %w", err))
|
||||
}
|
||||
}
|
||||
// **A build whose source is unchanged is never a move** (novox/hq issue 280): a rebuild made from
|
||||
// what the build the mesh stands on was made from registers that build's artifacts at the new
|
||||
// commit, so no machine is sent a new digest for a source nobody changed — an image is not
|
||||
@@ -172,7 +163,7 @@ func registerJudged(ctx context.Context, inv *inventory.Inventory, j takenBuild)
|
||||
return manifest, err
|
||||
}
|
||||
if stands != "" && stands != kept.ID && len(raw) > 0 {
|
||||
if was, err := catalogue.ParseManifest(raw); err == nil && was.Module == manifest.Module {
|
||||
if was, err := catalogue.Decode(raw); err == nil && was.Module == manifest.Module {
|
||||
fmt.Printf("%s at %s was made from the source %s was: registered with its artifacts, no move\n",
|
||||
manifest.Module, short(j.commit), stands)
|
||||
manifest = was
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
func TestRegistrationRefusesADefinitionNamingAnInstallation(t *testing.T) {
|
||||
// novox/hq ADR 0155: the check moves to registration once the catalogue passes it. Both
|
||||
// ways in — `module add` and a build's result — go through this, and a name declared on
|
||||
// purpose passes with its reason.
|
||||
named := catalogue.Manifest{Module: "idp", Resources: []map[string]any{
|
||||
{"id": "server", "type": "container", "image": "x@sha256:aa",
|
||||
"env": map[string]any{"KC_HOSTNAME": "https://login.mesh-one.be"}},
|
||||
}}
|
||||
err := namesNoInstallation(named)
|
||||
if err == nil || !strings.Contains(err.Error(), "login.mesh-one.be") ||
|
||||
!strings.Contains(err.Error(), catalogue.NamesOnPurpose) {
|
||||
t.Fatalf("a definition naming an installation is refused with the name and the way out; got %v", err)
|
||||
}
|
||||
meant := catalogue.Manifest{Module: "site", Resources: []map[string]any{
|
||||
{"id": "server", "type": "container", "image": "registry.mesh-one.be/org/site@sha256:cc",
|
||||
catalogue.NamesOnPurpose: map[string]any{
|
||||
"registry.mesh-one.be": "built outside the mesh until its repository is a build source here"}},
|
||||
}}
|
||||
if err := namesNoInstallation(meant); err != nil {
|
||||
t.Fatalf("a name declared on purpose passes; got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -171,6 +171,8 @@ func TestAKeyIsTakenInOnTheIndexersVerdictAndNeverJudgedHere(t *testing.T) {
|
||||
holds string
|
||||
}{
|
||||
{"an unread word", `{"module":"x","version":"1","not-a-word-yet":true,` + tail, false, ""},
|
||||
{"a fallback to a predefined key, no list at hand", `{"module":"x","version":"1","config":{"font-size":{"type":"number",
|
||||
"description":"size","default":10,"predefined":"font-size"}},` + tail, false, ""},
|
||||
{"no verdict is accepted", `{"module":"x","version":"1",` + tail, false, ""},
|
||||
{"a refusal is held and named", `{"module":"x","version":"1",` + tail, true, "names a seat nothing declares"},
|
||||
} {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -102,17 +101,15 @@ func moduleCommand(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// A manifest handed over by hand takes each template's text from the tree beside it (novox/hq ADR 0349 §2).
|
||||
if m, err = catalogue.WithTemplatesFrom(m, filepath.Dir(positionals[0])); err != nil {
|
||||
return err
|
||||
// A manifest handed over by hand is not judged here (ADR 0578 §3): a templated one has no build to read its
|
||||
// texts from, so it is refused by name.
|
||||
if p := catalogue.HeldTemplateProblem(m); p != "" {
|
||||
return errors.New(p)
|
||||
}
|
||||
from, err := whereItComesFrom(*repo, *ref, *commit, *path, *self)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := namesNoInstallation(m); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := inv.RegisterModule(ctx, m, from); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1388,29 +1385,6 @@ func whereItComesFrom(repository, ref, commit, path string, self bool) (inventor
|
||||
return from, nil
|
||||
}
|
||||
|
||||
// namesNoInstallation is the mesh refusing a definition that names an installation, at the moment
|
||||
// it would enter the catalogue (novox/hq ADR 0112, ADR 0155). `module check` says the same thing
|
||||
// earlier, where the author is; this is the last moment the mesh can still say no, and a
|
||||
// definition that got past the check — written elsewhere, or checked by nobody — is refused here
|
||||
// in the same words. A name meant on purpose is declared with its reason and passes.
|
||||
func namesNoInstallation(m catalogue.Manifest) error {
|
||||
if problems := catalogue.TrustProblems(m); len(problems) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(problems, "; "))
|
||||
}
|
||||
// And a module that declares a setting names the keys of its mergeable files (novox/hq ADR 0326
|
||||
// §3, issue 574): judged where a definition is written, never where a stored build is read again.
|
||||
if problems := catalogue.WrittenSettingsProblems(m); len(problems) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(problems, "; "))
|
||||
}
|
||||
named := catalogue.InstallationProblems(m)
|
||||
if len(named) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s names an installation, and a definition names none — declare a name meant "+
|
||||
"on purpose under %s with its reason, or take it out:\n - %s",
|
||||
m.Module, catalogue.NamesOnPurpose, strings.Join(named, "\n - "))
|
||||
}
|
||||
|
||||
// seatClaimed is one seat a module claims, as its runtime needs it: the name, the scope (a
|
||||
// node-scoped seat's verb carries the machine, design 33 §4) and the verbs the seat promises.
|
||||
type seatClaimed struct {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// ADR 0326 §3 is judged where a definition is written (novox/hq issue 574): namesNoInstallation, which `module add`, a build taken in and the merge gate's registration run.
|
||||
func TestWhereADefinitionIsWrittenADeclaringModuleNamesItsFilesKeys(t *testing.T) {
|
||||
const silent = `{"module":"mail-client","version":"1",
|
||||
"settings":{"prefs":{"kind":"preference","default":"// none","why":"its own defaults until a machine names one"}},
|
||||
"resources":[
|
||||
{"id":"user-js","type":"file","path":"/var/lib/mail-client/user.js","mode":"0644","content":"${setting:prefs}\n"},
|
||||
{"id":"accounts","type":"file","path":"/var/lib/mail-client/accounts.json","mode":"0600","merge":"json",
|
||||
"content":"{}\n"%s}]}`
|
||||
dir := t.TempDir()
|
||||
bad := filepath.Join(dir, "bad.json")
|
||||
os.WriteFile(bad, []byte(strings.Replace(silent, "%s", "", 1)), 0o600)
|
||||
|
||||
raw, _ := os.ReadFile(bad)
|
||||
m, err := catalogue.ParseManifest(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("reading it is not where it is judged: %v", err)
|
||||
}
|
||||
if err := namesNoInstallation(m); err == nil || !strings.Contains(err.Error(), "names no takes-settings") {
|
||||
t.Fatalf("registration passed it: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user