Let a file resource name a template file in its module's repository
A file's text may live in files/<name> beside the manifest instead of inline, so modules can move their files out of module.json (ADR 0349 clause 2, issue 654). The build packs files/ into the module's declared archive and reads each template back from the packed bytes, proven by the digest, into content: the manifest the mesh holds, composition and the node-engine stay as they are. module check, the merge gate and module add read templates from the tree beside the manifest, so a change to a template alone is judged. A file naming both template and content, a template outside files/, a missing one, and a held manifest that still names one are refused by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,11 @@ func moduleCheckFor(paths []string, longestMachine int, out io.Writer) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m, err := catalogue.ParseManifest(raw)
|
m, err := catalogue.ParseManifest(raw)
|
||||||
|
if err == nil {
|
||||||
|
// 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.
|
||||||
|
m, err = catalogue.WithTemplatesFrom(m, filepath.Dir(path))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(out, "%s: %v\n", path, err)
|
fmt.Fprintf(out, "%s: %v\n", path, err)
|
||||||
failed++
|
failed++
|
||||||
|
|||||||
@@ -576,6 +576,11 @@ func shelfWithChange(base map[string]catalogue.Manifest, sources map[string]inve
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m, err := catalogue.ParseManifest(raw)
|
m, err := catalogue.ParseManifest(raw)
|
||||||
|
if err == nil {
|
||||||
|
// 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.
|
||||||
|
m, err = catalogue.WithTemplatesFrom(m, filepath.Dir(path))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if inScope(dir) {
|
if inScope(dir) {
|
||||||
failures = append(failures, fmt.Sprintf("%s: the controller judging this (%s) refuses it — %s",
|
failures = append(failures, fmt.Sprintf("%s: the controller judging this (%s) refuses it — %s",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -127,6 +128,10 @@ func moduleCommand(ctx context.Context, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
||||||
|
}
|
||||||
from, err := whereItComesFrom(*repo, *ref, *commit, *path, *self)
|
from, err := whereItComesFrom(*repo, *ref, *commit, *path, *self)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -268,7 +268,14 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
|||||||
}
|
}
|
||||||
// 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.
|
||||||
if problems := catalogue.WrittenSettingsProblems(manifest); len(problems) > 0 {
|
// Judged with each template's text in its file, as written in the tree (novox/hq ADR 0349 §2): a template the
|
||||||
|
// module's directory lacks is refused here, before anything is built.
|
||||||
|
judged, err := catalogue.WithTemplatesFrom(manifest, within)
|
||||||
|
if err != nil {
|
||||||
|
say("manifest", "INVALID: %v", err)
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
if problems := catalogue.WrittenSettingsProblems(judged); len(problems) > 0 {
|
||||||
err := fmt.Errorf("%s", strings.Join(problems, "; "))
|
err := fmt.Errorf("%s", strings.Join(problems, "; "))
|
||||||
say("manifest", "INVALID: %v", err)
|
say("manifest", "INVALID: %v", err)
|
||||||
return Result{}, err
|
return Result{}, err
|
||||||
@@ -410,6 +417,11 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each template's text, read back from the archive the build made or took (novox/hq ADR 0349 §2).
|
||||||
|
if built, err = withTemplateTexts(manifest, within, built); err != nil {
|
||||||
|
say("templates", "FAILED: %v", err)
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
resolved, err := manifest.Resolve(built)
|
resolved, err := manifest.Resolve(built)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
say("resolve", "FAILED: %v", err)
|
say("resolve", "FAILED: %v", err)
|
||||||
|
|||||||
@@ -73,8 +73,14 @@ const reusedModule = `{"module":"meshboard","version":"1",
|
|||||||
// aModuleAt is a repository holding the module under mod/, whose directory git names as tree, on its trunk.
|
// aModuleAt is a repository holding the module under mod/, whose directory git names as tree, on its trunk.
|
||||||
func aModuleAt(t *testing.T, tree string) (*recorded, Runner, string) {
|
func aModuleAt(t *testing.T, tree string) (*recorded, Runner, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
r := &recorded{tree: tree, contents: map[string]string{"mod/" + ManifestName: reusedModule,
|
return aModuleWith(t, tree, map[string]string{"mod/" + ManifestName: reusedModule,
|
||||||
"mod/Dockerfile": "FROM scratch", "mod/files/theme.conf": "dark"}}
|
"mod/Dockerfile": "FROM scratch", "mod/files/theme.conf": "dark"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// aModuleWith is aModuleAt with the repository's files given.
|
||||||
|
func aModuleWith(t *testing.T, tree string, contents map[string]string) (*recorded, Runner, string) {
|
||||||
|
t.Helper()
|
||||||
|
r := &recorded{tree: tree, contents: contents}
|
||||||
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
|
run := func(ctx context.Context, dir, name string, args ...string) (string, error) {
|
||||||
if name == "git" && len(args) > 0 && args[0] == "symbolic-ref" {
|
if name == "git" && len(args) > 0 && args[0] == "symbolic-ref" {
|
||||||
r.ran = append(r.ran, name+" "+strings.Join(args, " "))
|
r.ran = append(r.ran, name+" "+strings.Join(args, " "))
|
||||||
@@ -100,6 +106,12 @@ var aPull = PullRef{Owner: "novox", Repo: "meshboard", Number: 7}
|
|||||||
func aCheckBuilt(t *testing.T, scratch *aScratch, tree string) CheckBuilt {
|
func aCheckBuilt(t *testing.T, scratch *aScratch, tree string) CheckBuilt {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
r, run, workspace := aModuleAt(t, tree)
|
r, run, workspace := aModuleAt(t, tree)
|
||||||
|
return aCheckBuiltBy(t, scratch, r, run, workspace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// aCheckBuiltBy is aCheckBuilt of the repository given.
|
||||||
|
func aCheckBuiltBy(t *testing.T, scratch *aScratch, r *recorded, run Runner, workspace string) CheckBuilt {
|
||||||
|
t.Helper()
|
||||||
spec := CheckSpec{ID: "build-1", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number,
|
spec := CheckSpec{ID: "build-1", Owner: aPull.Owner, Repo: aPull.Repo, Number: aPull.Number,
|
||||||
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
|
Builds: []CheckBuildSpec{{Module: "meshboard", Repository: "https://forge.invalid/meshboard.git", Path: "mod",
|
||||||
Ref: "main"}}}
|
Ref: "main"}}}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ type Built struct {
|
|||||||
// Launchers are, for a bundle in an interpreted language, the executable the build wrote beside
|
// Launchers are, for a bundle in an interpreted language, the executable the build wrote beside
|
||||||
// each entrypoint, by entrypoint (novox/hq ADR 0193): what the node's runtime starts to serve it.
|
// each entrypoint, by entrypoint (novox/hq ADR 0193): what the node's runtime starts to serve it.
|
||||||
Launchers map[string]string
|
Launchers map[string]string
|
||||||
|
// Texts are, for the archive a module packs its templates into (TemplatesArtifact), each file's text as the
|
||||||
|
// build read it back from the packed archive, by its path inside it (novox/hq ADR 0349 §2). Never carried on
|
||||||
|
// the wire or kept: Resolve reads them into the files' content, which is what the mesh holds.
|
||||||
|
Texts map[string]string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve fills a manifest's resources in from what was built.
|
// Resolve fills a manifest's resources in from what was built.
|
||||||
@@ -71,6 +75,26 @@ func (m Manifest) Resolve(built []Built) (Manifest, error) {
|
|||||||
m.Module, strings.Join(missing, " and "), oneOrOther(len(missing)))
|
m.Module, strings.Join(missing, " and "), oneOrOther(len(missing)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **Each template's text, as the build read it back from its templates archive** (novox/hq ADR 0349 §2): into
|
||||||
|
// the file's content, so what the mesh holds and composes is what the same text inline would be.
|
||||||
|
if m.Templated() {
|
||||||
|
name := TemplatesArtifact(m)
|
||||||
|
texts := by[name].Texts
|
||||||
|
if name == "" || texts == nil {
|
||||||
|
return Manifest{}, fmt.Errorf("%s names templates, and the build carried no text read back from the "+
|
||||||
|
"archive it packs %s/ into (%q): the builder that built it does not read templates",
|
||||||
|
m.Module, TemplatesDir, name)
|
||||||
|
}
|
||||||
|
filled, err := fillTemplates(m.Module, m.Resources, func(template string) (string, bool) {
|
||||||
|
t, ok := texts[strings.TrimPrefix(template, TemplatesDir+"/")]
|
||||||
|
return t, ok
|
||||||
|
}, fmt.Sprintf("the archive %q", name))
|
||||||
|
if err != nil {
|
||||||
|
return Manifest{}, err
|
||||||
|
}
|
||||||
|
m.Resources = filled
|
||||||
|
}
|
||||||
|
|
||||||
out := m
|
out := m
|
||||||
out.Build = nil
|
out.Build = nil
|
||||||
out.Resources = nil
|
out.Resources = nil
|
||||||
|
|||||||
@@ -1853,6 +1853,9 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
problems = append(problems, m.Build.problems(m.Module)...)
|
problems = append(problems, m.Build.problems(m.Module)...)
|
||||||
|
// A file's text in a template file of the module's repository (novox/hq ADR 0349 §2): only a file, never beside
|
||||||
|
// content, only under files/, and packed by the module's build.
|
||||||
|
problems = append(problems, templateProblems(m)...)
|
||||||
problems = append(problems, undeliveredBundles(m)...)
|
problems = append(problems, undeliveredBundles(m)...)
|
||||||
// **What provides the artifact store cannot be delivered through it** (novox/hq 04-ISSUES/029).
|
// **What provides the artifact store cannot be delivered through it** (novox/hq 04-ISSUES/029).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ func (i *Inventory) RegisterModule(ctx context.Context, m catalogue.Manifest, fr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// **The manifest the mesh holds carries each file's text** (novox/hq ADR 0349 §2): one still naming a template
|
||||||
|
// would compose a file with no text, so it is refused by name — a build by a builder that predates templates.
|
||||||
|
if why := catalogue.HeldTemplateProblem(m); why != "" {
|
||||||
|
return fmt.Errorf("%s is not registered: %s", m.Module, why)
|
||||||
|
}
|
||||||
raw, err := json.Marshal(m)
|
raw, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user