Judge every manifest against the list of predefined keys of its tree, and carry the list from a build
Mesh and node config may hold values only for keys on a fixed, reviewed list (hq ADR 0348 clause 7, design 57 package K2, issue 661). A build reads predefined.json at the root of the repository at the commit it builds and carries it with the commit's generation; module check and the merge gate read it from the tree. (Part 1 of 2: the changes to existing files.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -418,6 +418,11 @@ func answer(ctx context.Context, publisher builder.Publisher, on, workspace stri
|
|||||||
for _, s := range built.Sources {
|
for _, s := range built.Sources {
|
||||||
result.Sources = append(result.Sources, link.BuildSource{Repository: s.Repository, Ref: s.Ref, Paths: s.Paths})
|
result.Sources = append(result.Sources, link.BuildSource{Repository: s.Repository, Ref: s.Ref, Paths: s.Paths})
|
||||||
}
|
}
|
||||||
|
// The list of predefined keys the manifest was judged against, for the controller to publish
|
||||||
|
// (novox/hq ADR 0348 §7).
|
||||||
|
if p := built.Predefined; p != nil {
|
||||||
|
result.Predefined = &link.PredefinedList{Keys: p.Keys, Generation: p.Generation}
|
||||||
|
}
|
||||||
say("built", built.Manifest.Module+" from "+short(built.Commit))
|
say("built", built.Manifest.Module+" from "+short(built.Commit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,8 +183,10 @@ func buildFrom(result link.BuildResult) inventory.Build {
|
|||||||
kept.Manifest = recordedManifest(result.Manifest, announced)
|
kept.Manifest = recordedManifest(result.Manifest, announced)
|
||||||
// The module name comes from the manifest, which only exists when the build got that far.
|
// The module name comes from the manifest, which only exists when the build got that far.
|
||||||
if len(kept.Manifest) > 0 {
|
if len(kept.Manifest) > 0 {
|
||||||
if m, err := catalogue.ParseManifest(kept.Manifest); err == nil {
|
if list, err := judgedWith(result); err == nil {
|
||||||
kept.Module = m.Module
|
if m, err := catalogue.ParseManifestWith(kept.Manifest, list); err == nil {
|
||||||
|
kept.Module = m.Module
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return kept
|
return kept
|
||||||
@@ -603,6 +605,11 @@ func saysWhenThePolicyActs(ctx context.Context, inv *inventory.Inventory, module
|
|||||||
// written with the same values.
|
// written with the same values.
|
||||||
func takeIn(ctx context.Context, inv *inventory.Inventory, result link.BuildResult) (
|
func takeIn(ctx context.Context, inv *inventory.Inventory, result link.BuildResult) (
|
||||||
catalogue.Manifest, inventory.Build, error) {
|
catalogue.Manifest, inventory.Build, error) {
|
||||||
|
// The list of predefined keys as the store keeps it now: another controller process may have kept a newer one.
|
||||||
|
if err := loadPredefined(ctx, inv); err != nil {
|
||||||
|
return catalogue.Manifest{}, inventory.Build{}, fmt.Errorf("the list of predefined keys kept cannot be "+
|
||||||
|
"read: %w", err)
|
||||||
|
}
|
||||||
kept := buildFrom(result)
|
kept := buildFrom(result)
|
||||||
if err := inv.RecordBuild(ctx, kept); err != nil {
|
if err := inv.RecordBuild(ctx, kept); err != nil {
|
||||||
return catalogue.Manifest{}, kept, err
|
return catalogue.Manifest{}, kept, err
|
||||||
@@ -613,7 +620,15 @@ func takeIn(ctx context.Context, inv *inventory.Inventory, result link.BuildResu
|
|||||||
return catalogue.Manifest{}, kept, fmt.Errorf("%s could not build %s:\n%s",
|
return catalogue.Manifest{}, kept, fmt.Errorf("%s could not build %s:\n%s",
|
||||||
result.On, result.Repository, result.Failed)
|
result.On, result.Repository, result.Failed)
|
||||||
}
|
}
|
||||||
manifest, err := catalogue.ParseManifest(kept.Manifest)
|
// Judged first against the list the build was judged against: the catalogue's at the commit built (novox/hq
|
||||||
|
// ADR 0348 §7), so a module naming a key its own commit adds is read; and again below against the list the
|
||||||
|
// mesh publishes, before it is registered.
|
||||||
|
list, err := judgedWith(result)
|
||||||
|
if err != nil {
|
||||||
|
return catalogue.Manifest{}, kept, fmt.Errorf("%s built %s (%s), recorded and not registered: %w", result.On,
|
||||||
|
result.Repository, short(result.Commit), err)
|
||||||
|
}
|
||||||
|
manifest, err := catalogue.ParseManifestWith(kept.Manifest, list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return catalogue.Manifest{}, kept, fmt.Errorf("%s built %s and what came back is not a manifest: %w",
|
return catalogue.Manifest{}, kept, fmt.Errorf("%s built %s and what came back is not a manifest: %w",
|
||||||
result.On, result.Repository, err)
|
result.On, result.Repository, err)
|
||||||
@@ -687,6 +702,23 @@ func takeIn(ctx context.Context, inv *inventory.Inventory, result link.BuildResu
|
|||||||
"back: it is recorded and not registered again — a newer build is", result.On, manifest.Module,
|
"back: it is recorded and not registered again — a newer build is", result.On, manifest.Module,
|
||||||
short(result.Commit))
|
short(result.Commit))
|
||||||
}
|
}
|
||||||
|
// **The newest trunk build's list of predefined keys is published** (novox/hq ADR 0348 §7): a build of the
|
||||||
|
// catalogue that may be registered keeps its list when its commit is newer than the one kept. 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.
|
||||||
|
if carried, err := carriedList(result); err != nil {
|
||||||
|
return manifest, kept, err
|
||||||
|
} else if carried != nil {
|
||||||
|
if err := publishPredefined(ctx, inv, result, carried); err != nil {
|
||||||
|
fmt.Printf("%s: the list of predefined keys it carried is not published: %v\n", result.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if manifest.NamesPredefined() {
|
||||||
|
if _, err := catalogue.ParseManifest(kept.Manifest); err != nil {
|
||||||
|
return manifest, kept, fmt.Errorf("%s built %s (%s), recorded and not registered: judged against the "+
|
||||||
|
"list of predefined keys the mesh publishes, %w", result.On, manifest.Module, short(result.Commit), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
// **A build whose source is unchanged is never a move** (novox/hq issue 280): a rebuild made from
|
// **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
|
// 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
|
// commit, so no machine is sent a new digest for a source nobody changed — an image is not
|
||||||
|
|||||||
@@ -47,6 +47,24 @@ func moduleCheckFor(paths []string, longestMachine int, out io.Writer) error {
|
|||||||
shelf := catalogue.Shelf{}
|
shelf := catalogue.Shelf{}
|
||||||
faulted := map[string]bool{}
|
faulted := map[string]bool{}
|
||||||
failed := 0
|
failed := 0
|
||||||
|
// The list of predefined keys at the root of the repository above each manifest (novox/hq ADR 0348 §7), read
|
||||||
|
// once per repository: a manifest naming a key is judged against the list its own tree holds.
|
||||||
|
lists := map[string]catalogue.PredefinedList{}
|
||||||
|
listOf := func(path string) (catalogue.PredefinedList, error) {
|
||||||
|
root := catalogue.RepositoryRoot(filepath.Dir(path))
|
||||||
|
if root == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if list, read := lists[root]; read {
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
list, err := catalogue.ReadPredefined(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lists[root] = list
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,7 +72,13 @@ func moduleCheckFor(paths []string, longestMachine int, out io.Writer) error {
|
|||||||
failed++
|
failed++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m, err := catalogue.ParseManifest(raw)
|
list, err := listOf(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(out, "%s: %v\n", path, err)
|
||||||
|
failed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m, err := catalogue.ParseManifestWith(raw, list)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Each template's text read from the tree beside the manifest, and judged as inline content is
|
// Each template's text read from the tree beside the manifest, and judged as inline content is
|
||||||
// (novox/hq ADR 0349 §2): a missing template is refused by name.
|
// (novox/hq ADR 0349 §2): a missing template is refused by name.
|
||||||
|
|||||||
@@ -550,8 +550,17 @@ func shelfWithChange(base map[string]catalogue.Manifest, sources map[string]inve
|
|||||||
out[k] = m
|
out[k] = m
|
||||||
}
|
}
|
||||||
var failures []string
|
var failures []string
|
||||||
|
// The list of predefined keys at the tree's root (novox/hq ADR 0348 §7): what the build of this tree judges each
|
||||||
|
// manifest against and registration publishes, so what the gate judges each against too. A list that is
|
||||||
|
// wrong is refused by name, and every manifest is then judged against none.
|
||||||
|
predefined, err := catalogue.ReadPredefined(tree)
|
||||||
|
if err != nil {
|
||||||
|
failures = append(failures, fmt.Sprintf("%s: the controller judging this (%s) refuses it — %s",
|
||||||
|
catalogue.PredefinedFile, version, oneLine(err.Error())))
|
||||||
|
predefined = nil
|
||||||
|
}
|
||||||
found := map[string]string{} // module → its directory in the tree
|
found := map[string]string{} // module → its directory in the tree
|
||||||
err := filepath.WalkDir(tree, func(path string, d fs.DirEntry, err error) error {
|
err = filepath.WalkDir(tree, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -575,7 +584,7 @@ func shelfWithChange(base map[string]catalogue.Manifest, sources map[string]inve
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m, err := catalogue.ParseManifest(raw)
|
m, err := catalogue.ParseManifestWith(raw, predefined)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Each template's text from the tree beside the manifest, as the build reads it back from the archive it
|
// Each template's text from the tree beside the manifest, as the build reads it back from the archive it
|
||||||
// packs (novox/hq ADR 0349 §2): so a change to a template alone is a change to its module.
|
// packs (novox/hq ADR 0349 §2): so a change to a template alone is a change to its module.
|
||||||
@@ -610,7 +619,7 @@ func shelfWithChange(base map[string]catalogue.Manifest, sources map[string]inve
|
|||||||
// As registration reads a built manifest: strictly, again (takeIn).
|
// As registration reads a built manifest: strictly, again (takeIn).
|
||||||
var raw []byte
|
var raw []byte
|
||||||
if raw, err = json.Marshal(resolved); err == nil {
|
if raw, err = json.Marshal(resolved); err == nil {
|
||||||
resolved, err = catalogue.ParseManifest(raw)
|
resolved, err = catalogue.ParseManifestWith(raw, predefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/novox/mesh-controller/internal/catalogue"
|
"github.com/novox/mesh-controller/internal/catalogue"
|
||||||
@@ -124,6 +125,11 @@ func openInventory(ctx context.Context) (*inventory.Inventory, error) {
|
|||||||
if aliases, err := inv.Aliases(ctx); err == nil {
|
if aliases, err := inv.Aliases(ctx); err == nil {
|
||||||
catalogue.UseAliases(aliases)
|
catalogue.UseAliases(aliases)
|
||||||
}
|
}
|
||||||
|
// And the list of predefined keys the mesh publishes (novox/hq ADR 0348 §7), which every manifest read again
|
||||||
|
// is judged against. A store a migration has not reached yet keeps none, and nothing names a key then.
|
||||||
|
if err := loadPredefined(ctx, inv); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "the list of predefined keys kept cannot be read: %v\n", err)
|
||||||
|
}
|
||||||
return inv, nil
|
return inv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ type Result struct {
|
|||||||
// recipe and the package it compiles; for any other, nothing is said and the whole of what the build
|
// recipe and the package it compiles; for any other, nothing is said and the whole of what the build
|
||||||
// sees stays its source, as before.
|
// sees stays its source, as before.
|
||||||
Sources []BuildSource
|
Sources []BuildSource
|
||||||
|
|
||||||
|
// Predefined is the list of predefined keys at the root of the repository at the commit built (novox/hq ADR
|
||||||
|
// 0348 §7), which the manifest was judged against; nil for a repository that holds none.
|
||||||
|
Predefined *Predefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predefined is the list of predefined keys a build read: its text, canonical (catalogue.PredefinedList), and
|
||||||
|
// the generation of the commit built — how many commits it stands on, which orders two builds of one trunk.
|
||||||
|
type Predefined struct {
|
||||||
|
Keys []byte
|
||||||
|
Generation int
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildSource is the build source a build read in one repository (novox/hq ADR 0267).
|
// BuildSource is the build source a build read in one repository (novox/hq ADR 0267).
|
||||||
@@ -261,11 +272,28 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
|||||||
"%s has no %s at %s, so there is nothing saying what it is: %w",
|
"%s has no %s at %s, so there is nothing saying what it is: %w",
|
||||||
repository, ManifestName, describe(path), err)
|
repository, ManifestName, describe(path), err)
|
||||||
}
|
}
|
||||||
manifest, err := catalogue.ParseManifest(raw)
|
// **The list of predefined keys at the commit built** (novox/hq ADR 0348 §7): predefined.json at the
|
||||||
|
// repository's root, read strictly; a list that is wrong fails the build, and the manifest is judged against
|
||||||
|
// the list of its own tree, never against one the mesh published from another commit.
|
||||||
|
predefined, err := catalogue.ReadPredefined(tree)
|
||||||
|
if err != nil {
|
||||||
|
say("predefined", "INVALID: %v", err)
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
manifest, err := catalogue.ParseManifestWith(raw, predefined)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
say("manifest", "INVALID: %v", err)
|
say("manifest", "INVALID: %v", err)
|
||||||
return Result{}, err
|
return Result{}, err
|
||||||
}
|
}
|
||||||
|
var listed *Predefined
|
||||||
|
if predefined != nil {
|
||||||
|
text, err := predefined.Canonical()
|
||||||
|
if err != nil {
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
listed = &Predefined{Keys: text, Generation: generationOf(ctx, run, tree, commit)}
|
||||||
|
say("predefined", "%d key(s) at %s, generation %d", len(predefined), short(commit), listed.Generation)
|
||||||
|
}
|
||||||
// What is judged only where a definition is written (novox/hq ADR 0326 §3, issue 574): a build is
|
// What is judged only where a definition is written (novox/hq ADR 0326 §3, issue 574): a build is
|
||||||
// where a changed definition is first read, so it is refused here before anything is made of it.
|
// where a changed definition is first read, so it is refused here before anything is made of it.
|
||||||
// Judged with each template's text in its file, as written in the tree (novox/hq ADR 0349 §2): a template the
|
// Judged with each template's text in its file, as written in the tree (novox/hq ADR 0349 §2): a template the
|
||||||
@@ -291,6 +319,11 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
|||||||
if src.tree, err = gitTree(ctx, run, tree, path); err != nil {
|
if src.tree, err = gitTree(ctx, run, tree, path); err != nil {
|
||||||
src.notPinned("its tree could not be named: " + err.Error())
|
src.notPinned("its tree could not be named: " + err.Error())
|
||||||
}
|
}
|
||||||
|
// A module falling back to a predefined key was judged against the list, so a change to the list is a change
|
||||||
|
// to it: the planner rebuilds it, and its build publishes the list (ADR 0348 §7).
|
||||||
|
if predefined != nil && manifest.NamesPredefined() {
|
||||||
|
src.rootHas(catalogue.PredefinedFile)
|
||||||
|
}
|
||||||
|
|
||||||
// A build-time credential, written into the build context as .npmrc, but ONLY for a module that
|
// A build-time credential, written into the build context as .npmrc, but ONLY for a module that
|
||||||
// asks for it: a `package` artifact (which publishes), or an image whose Dockerfile COPYs .npmrc.
|
// asks for it: a `package` artifact (which publishes), or an image whose Dockerfile COPYs .npmrc.
|
||||||
@@ -451,7 +484,23 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
|||||||
return Result{Manifest: resolved, Commit: commit, Built: built,
|
return Result{Manifest: resolved, Commit: commit, Built: built,
|
||||||
Against: against(within, manifest, stoodOn), Read: readBy(manifest), Source: fingerprint,
|
Against: against(within, manifest, stoodOn), Read: readBy(manifest), Source: fingerprint,
|
||||||
Trunk: trunk, OnTrunk: onTrunk, Branches: branches, Sources: sources,
|
Trunk: trunk, OnTrunk: onTrunk, Branches: branches, Sources: sources,
|
||||||
ReuseKey: key.Key, ReuseParts: key.Parts, NoKey: key.Why, Reused: reused, Why: builtWhy}, nil
|
ReuseKey: key.Key, ReuseParts: key.Parts, NoKey: key.Why, Reused: reused, Why: builtWhy,
|
||||||
|
Predefined: listed}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generationOf is how many commits the commit built stands on, itself included: of two commits on one trunk the
|
||||||
|
// later has more, so the controller keeps the list of the newer one whatever order it hears them in (ADR 0348
|
||||||
|
// §7). 0 when git could not say.
|
||||||
|
func generationOf(ctx context.Context, run Runner, tree, commit string) int {
|
||||||
|
out, err := run(ctx, tree, "git", "rev-list", "--count", commit)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(strings.TrimSpace(out))
|
||||||
|
if err != nil || n < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// takeACheckBuild is the walk's look for a check build of its key (novox/hq ADR 0350 §2, §3): the key taken by
|
// takeACheckBuild is the walk's look for a check build of its key (novox/hq ADR 0350 §2, §3): the key taken by
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ func (r *recorded) run(_ context.Context, dir, name string, args ...string) (str
|
|||||||
return r.tree + "\n", nil
|
return r.tree + "\n", nil
|
||||||
case name == "git" && len(args) > 0 && args[0] == "rev-parse":
|
case name == "git" && len(args) > 0 && args[0] == "rev-parse":
|
||||||
return "c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ff\n", nil
|
return "c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ff\n", nil
|
||||||
|
case name == "git" && len(args) > 1 && args[0] == "rev-list" && args[1] == "--count":
|
||||||
|
return "42\n", nil
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ type sourceInputs struct {
|
|||||||
// whole when an artifact's is not known (novox/hq ADR 0267).
|
// whole when an artifact's is not known (novox/hq ADR 0267).
|
||||||
own map[string]bool
|
own map[string]bool
|
||||||
ownIsAll bool
|
ownIsAll bool
|
||||||
|
// root is the module's build source in its own repository outside its directory, relative to the
|
||||||
|
// repository's root: the list of predefined keys a manifest naming one was judged against (ADR 0348 §7).
|
||||||
|
root map[string]bool
|
||||||
// read is, per context (repository and ref), the build source read there; nil for one read whole.
|
// read is, per context (repository and ref), the build source read there; nil for one read whole.
|
||||||
read map[string]map[string]bool
|
read map[string]map[string]bool
|
||||||
readAs map[string]catalogue.ArtifactContext
|
readAs map[string]catalogue.ArtifactContext
|
||||||
@@ -86,6 +89,9 @@ func (s *sourceInputs) copy() *sourceInputs {
|
|||||||
for k, v := range s.own {
|
for k, v := range s.own {
|
||||||
c.own[k] = v
|
c.own[k] = v
|
||||||
}
|
}
|
||||||
|
for k, v := range s.root {
|
||||||
|
c.root[k] = v
|
||||||
|
}
|
||||||
for k, v := range s.read {
|
for k, v := range s.read {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
c.read[k] = nil
|
c.read[k] = nil
|
||||||
@@ -112,7 +118,7 @@ func (s *sourceInputs) contextOffHead() {
|
|||||||
|
|
||||||
func newSourceInputs(module string) *sourceInputs {
|
func newSourceInputs(module string) *sourceInputs {
|
||||||
return &sourceInputs{module: module, contexts: map[string]string{}, toolchains: map[string]string{},
|
return &sourceInputs{module: module, contexts: map[string]string{}, toolchains: map[string]string{},
|
||||||
own: map[string]bool{}, read: map[string]map[string]bool{}, readAs: map[string]catalogue.ArtifactContext{},
|
own: map[string]bool{}, root: map[string]bool{}, read: map[string]map[string]bool{}, readAs: map[string]catalogue.ArtifactContext{},
|
||||||
contextsAtHead: true}
|
contextsAtHead: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +132,16 @@ func (s *sourceInputs) ownHas(entries ...string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rootHas adds entries, relative to the repository's root, to the module's build source in its own repository.
|
||||||
|
func (s *sourceInputs) rootHas(entries ...string) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
s.root[e] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ownWhole says an artifact's build source in the module's own repository is not known: the module's
|
// ownWhole says an artifact's build source in the module's own repository is not known: the module's
|
||||||
// whole directory is its source, as it was before.
|
// whole directory is its source, as it was before.
|
||||||
func (s *sourceInputs) ownWhole() {
|
func (s *sourceInputs) ownWhole() {
|
||||||
@@ -178,6 +194,9 @@ func (s *sourceInputs) buildSources() []BuildSource {
|
|||||||
for e := range s.own {
|
for e := range s.own {
|
||||||
own = append(own, withPrefix(s.prefix, e))
|
own = append(own, withPrefix(s.prefix, e))
|
||||||
}
|
}
|
||||||
|
for e := range s.root {
|
||||||
|
own = append(own, e)
|
||||||
|
}
|
||||||
sort.Strings(own)
|
sort.Strings(own)
|
||||||
out = append(out, BuildSource{Paths: compactSorted(own)})
|
out = append(out, BuildSource{Paths: compactSorted(own)})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,18 +61,6 @@ const (
|
|||||||
// accountFacts are what the mesh places for a module's account, never config values (ADR 0348 §11).
|
// accountFacts are what the mesh places for a module's account, never config values (ADR 0348 §11).
|
||||||
var accountFacts = map[string]bool{"puid": true, "pgid": true}
|
var accountFacts = map[string]bool{"puid": true, "pgid": true}
|
||||||
|
|
||||||
// PredefinedKey is one key of the catalogue's list of predefined keys, as far as a module's value falling back
|
|
||||||
// to it is judged here: its type, and whether it is trusted (ADR 0348 §7, §8).
|
|
||||||
type PredefinedKey struct {
|
|
||||||
Type string
|
|
||||||
Trusted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// predefinedKeys is the list of predefined keys, nil until it is present. The list (`predefined.json` at the
|
|
||||||
// catalogue's root) arrives with design 57's package K2 (issue 661); until then a value naming one is refused
|
|
||||||
// saying the list is not yet present.
|
|
||||||
var predefinedKeys map[string]PredefinedKey
|
|
||||||
|
|
||||||
// configForms is which of the two forms a manifest was written in, by the words of each it uses.
|
// configForms is which of the two forms a manifest was written in, by the words of each it uses.
|
||||||
type configForms struct {
|
type configForms struct {
|
||||||
config, settings []string
|
config, settings []string
|
||||||
@@ -259,12 +247,13 @@ func ConfigProblems(m Manifest) []string {
|
|||||||
"(novox/hq ADR 0348 §4, ADR 0112)", word)
|
"(novox/hq ADR 0348 §4, ADR 0112)", word)
|
||||||
}
|
}
|
||||||
if d.Predefined != "" {
|
if d.Predefined != "" {
|
||||||
p, listed := predefinedKeys[d.Predefined]
|
p, listed := m.predefined[d.Predefined]
|
||||||
switch {
|
switch {
|
||||||
case predefinedKeys == nil:
|
case m.predefined == nil:
|
||||||
say("falls back to the predefined key %q, and the list of predefined keys (the catalogue's "+
|
say("falls back to the predefined key %q, and the list of predefined keys (%s at the root of "+
|
||||||
"predefined.json, design 57 package K2) is not yet present: no value can fall back to one "+
|
"the catalogue's repository) is not present where it is judged: a value falls back only to a "+
|
||||||
"until it is; leave \"predefined\" out for now (novox/hq ADR 0348 §7)", d.Predefined)
|
"key on that list; judge it in the catalogue, or leave \"predefined\" out (novox/hq ADR 0348 §7)",
|
||||||
|
d.Predefined, PredefinedFile)
|
||||||
case !listed:
|
case !listed:
|
||||||
say("falls back to %q, which is not on the list of predefined keys: name a key on it, or leave "+
|
say("falls back to %q, which is not on the list of predefined keys: name a key on it, or leave "+
|
||||||
"\"predefined\" out (novox/hq ADR 0348 §7)", d.Predefined)
|
"\"predefined\" out (novox/hq ADR 0348 §7)", d.Predefined)
|
||||||
|
|||||||
@@ -157,20 +157,20 @@ func TestConfigRefusesAnObjectOrListNotTrusted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// withPredefined is the list of predefined keys as package K2 will give it, for the length of a test.
|
// withPredefined is the list of predefined keys the mesh published, for the length of a test.
|
||||||
func withPredefined(t *testing.T, keys map[string]PredefinedKey) {
|
func withPredefined(t *testing.T, keys PredefinedList) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
was := predefinedKeys
|
was := ReplacePublished(keys)
|
||||||
predefinedKeys = keys
|
t.Cleanup(func() { ReplacePublished(was) })
|
||||||
t.Cleanup(func() { predefinedKeys = was })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 5: a predefined key not on the list, or of another type. Until the list is present (package K2), a value
|
// Rule 5: a predefined key not on the list, or of another type. Where no list is present, a value naming one is
|
||||||
// naming one is refused saying so.
|
// refused saying so.
|
||||||
func TestConfigRefusesAPredefinedKeyNotOnTheListOrOfAnotherType(t *testing.T) {
|
func TestConfigRefusesAPredefinedKeyNotOnTheListOrOfAnotherType(t *testing.T) {
|
||||||
|
withPredefined(t, nil)
|
||||||
m := inConfigForm()
|
m := inConfigForm()
|
||||||
declaration(m, "font-size")["predefined"] = "font-size"
|
declaration(m, "font-size")["predefined"] = "font-size"
|
||||||
refused(t, m, `"font-size"`, "the list of predefined keys", "not yet present")
|
refused(t, m, `"font-size"`, "the list of predefined keys", "is not present")
|
||||||
|
|
||||||
withPredefined(t, map[string]PredefinedKey{"font-size": {Type: "number"}, "accent": {Type: "string"}})
|
withPredefined(t, map[string]PredefinedKey{"font-size": {Type: "number"}, "accent": {Type: "string"}})
|
||||||
if _, err := parseRaw(t, m); err != nil {
|
if _, err := parseRaw(t, m); err != nil {
|
||||||
@@ -186,9 +186,10 @@ func TestConfigRefusesAPredefinedKeyNotOnTheListOrOfAnotherType(t *testing.T) {
|
|||||||
|
|
||||||
// Rule 6: an untrusted value falling back to a trusted predefined key.
|
// Rule 6: an untrusted value falling back to a trusted predefined key.
|
||||||
func TestConfigRefusesAnUntrustedValueFallingBackToATrustedKey(t *testing.T) {
|
func TestConfigRefusesAnUntrustedValueFallingBackToATrustedKey(t *testing.T) {
|
||||||
|
withPredefined(t, nil)
|
||||||
m := inConfigForm()
|
m := inConfigForm()
|
||||||
declaration(m, "theme")["predefined"] = "theme"
|
declaration(m, "theme")["predefined"] = "theme"
|
||||||
refused(t, m, `"theme"`, "not yet present")
|
refused(t, m, `"theme"`, "is not present")
|
||||||
|
|
||||||
withPredefined(t, map[string]PredefinedKey{"theme": {Type: "string", Trusted: true}})
|
withPredefined(t, map[string]PredefinedKey{"theme": {Type: "string", Trusted: true}})
|
||||||
refused(t, m, `"theme"`, "trusted", "is not")
|
refused(t, m, `"theme"`, "trusted", "is not")
|
||||||
|
|||||||
@@ -541,6 +541,10 @@ type Manifest struct {
|
|||||||
// machine's declaration by name until the controller is updated (LeftOut).
|
// machine's declaration by name until the controller is updated (LeftOut).
|
||||||
unknown string
|
unknown string
|
||||||
|
|
||||||
|
// predefined is the list of predefined keys it was judged against (predefined.go, novox/hq ADR 0348 §7):
|
||||||
|
// read again — its templates filled from the tree — it is judged against the same list.
|
||||||
|
predefined PredefinedList
|
||||||
|
|
||||||
// bestEffort marks the view of a left-out module that only its backup lines are made from
|
// bestEffort marks the view of a left-out module that only its backup lines are made from
|
||||||
// (backupView, novox/hq ADR 0262): a line of it that cannot be placed is said in the plan's list of
|
// (backupView, novox/hq ADR 0262): a line of it that cannot be placed is said in the plan's list of
|
||||||
// what could not be placed, never an error that would cost the whole machine its declaration.
|
// what could not be placed, never an error that would cost the whole machine its declaration.
|
||||||
@@ -1600,6 +1604,17 @@ func SecretLocal(to, local string) string {
|
|||||||
func (m Manifest) UnknownField() string { return m.unknown }
|
func (m Manifest) UnknownField() string { return m.unknown }
|
||||||
|
|
||||||
func ParseManifest(raw []byte) (Manifest, error) {
|
func ParseManifest(raw []byte) (Manifest, error) {
|
||||||
|
return parseManifest(raw, PublishedPredefined())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseManifestWith is ParseManifest judging every value that falls back to a predefined key against the list
|
||||||
|
// given — the one read from the tree the manifest is in (predefined.go, novox/hq ADR 0348 §7) — where
|
||||||
|
// ParseManifest judges against the list the mesh published. Nil is no list: a value naming a key is refused.
|
||||||
|
func ParseManifestWith(raw []byte, predefined PredefinedList) (Manifest, error) {
|
||||||
|
return parseManifest(raw, predefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseManifest(raw []byte, predefined PredefinedList) (Manifest, error) {
|
||||||
var m Manifest
|
var m Manifest
|
||||||
// Strictly. **An unknown key is refused**, which is the discipline the host's declaration
|
// Strictly. **An unknown key is refused**, which is the discipline the host's declaration
|
||||||
// parser has and manifests lacked (novox/hq 04-ISSUES/003): a `scope:` key survived in five
|
// parser has and manifests lacked (novox/hq 04-ISSUES/003): a `scope:` key survived in five
|
||||||
@@ -1627,6 +1642,7 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
|||||||
}
|
}
|
||||||
return Manifest{}, fmt.Errorf("this is not a module manifest: %w", err)
|
return Manifest{}, fmt.Errorf("this is not a module manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
m.predefined = predefined
|
||||||
|
|
||||||
var problems []string
|
var problems []string
|
||||||
if !name.MatchString(m.Module) {
|
if !name.MatchString(m.Module) {
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func WithTemplatesFrom(m Manifest, dir string) (Manifest, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Manifest{}, err
|
return Manifest{}, err
|
||||||
}
|
}
|
||||||
return ParseManifest(raw)
|
return parseManifest(raw, m.predefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeldTemplateProblem is why a manifest cannot be held by the mesh because it still names a template, or "": the
|
// HeldTemplateProblem is why a manifest cannot be held by the mesh because it still names a template, or "": the
|
||||||
|
|||||||
@@ -334,6 +334,12 @@ type BuildResult struct {
|
|||||||
ReusedFrom *ReusedFrom `json:"reused-from,omitempty"`
|
ReusedFrom *ReusedFrom `json:"reused-from,omitempty"`
|
||||||
BuiltWhy string `json:"built-why,omitempty"`
|
BuiltWhy string `json:"built-why,omitempty"`
|
||||||
|
|
||||||
|
// Predefined is the list of predefined keys at the root of the repository at the commit built (novox/hq ADR
|
||||||
|
// 0348 §7), which the manifest was judged against: the controller publishes the catalogue's from a trunk
|
||||||
|
// build, the newest by Generation winning. Absent for a repository holding none, and from a builder that
|
||||||
|
// predates the list.
|
||||||
|
Predefined *PredefinedList `json:"predefined,omitempty"`
|
||||||
|
|
||||||
// Source is the request's, echoed: the seat form of the repository, for whoever registers.
|
// Source is the request's, echoed: the seat form of the repository, for whoever registers.
|
||||||
Source *SourceOnSeat `json:"source,omitempty"`
|
Source *SourceOnSeat `json:"source,omitempty"`
|
||||||
|
|
||||||
@@ -346,6 +352,14 @@ type BuildResult struct {
|
|||||||
Checked *CheckRequest `json:"check,omitempty"`
|
Checked *CheckRequest `json:"check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PredefinedList is the list of predefined keys a build read (novox/hq ADR 0348 §7): Keys its canonical text, a
|
||||||
|
// JSON object of keys, and Generation how many commits the commit built stands on — of two commits on one trunk
|
||||||
|
// the later has more, which is what orders two builds' lists; 0 when the builder could not say.
|
||||||
|
type PredefinedList struct {
|
||||||
|
Keys json.RawMessage `json:"keys"`
|
||||||
|
Generation int `json:"generation,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// ReadRepository is a repository a build read source from besides the module's own, at the branch,
|
// ReadRepository is a repository a build read source from besides the module's own, at the branch,
|
||||||
// tag or commit it read. Spelled here as well as in the catalogue and the inventory, for the reason
|
// tag or commit it read. Spelled here as well as in the catalogue and the inventory, for the reason
|
||||||
// MadeArtifact is: one direction of dependency.
|
// MadeArtifact is: one direction of dependency.
|
||||||
|
|||||||
Vendored
+3
@@ -64,3 +64,6 @@
|
|||||||
./internal/inventory TestPlacementMovesOutOfTheSettingsLayers
|
./internal/inventory TestPlacementMovesOutOfTheSettingsLayers
|
||||||
./internal/inventory TestASettingRoutesPlacementBesideTheLayer
|
./internal/inventory TestASettingRoutesPlacementBesideTheLayer
|
||||||
./cmd/mesh-controller TestADeclarationIsTheSameAfterPlacementMoves
|
./cmd/mesh-controller TestADeclarationIsTheSameAfterPlacementMoves
|
||||||
|
./internal/inventory TestANewerBuildsListReplacesTheKeptOneAndAnOlderOneHeardLaterDoesNot
|
||||||
|
./cmd/mesh-controller TestTheMergeGateJudgesEachManifestAgainstTheListOfTheTree
|
||||||
|
./cmd/mesh-controller TestRegistrationPublishesTheNewestTrunkListAndJudgesEveryModuleAgainstIt
|
||||||
|
|||||||
Reference in New Issue
Block a user