Read a module whose every artifact names its paths from those paths and its manifest alone, so a change elsewhere in its directory moves nothing (issue 1758, B1, ADR 0499 §5)

This commit is contained in:
2026-10-22 19:05:04 +02:00
parent a8387ebad8
commit e7e23c57cc
2 changed files with 78 additions and 2 deletions
+38 -2
View File
@@ -289,8 +289,14 @@ func buildOfKey(ctx context.Context, keys keyReads, t keyTake, kc keyContext) (
Commit: t.Trunk.Commit, On: k.Build.By, Path: keyPath(t.Trunk), Manifest: raw,
SourceFingerprint: k.Fingerprint, At: time.Now().UTC()}
// **Read from the manifest, fresh at every key** (the same rule build-agent says, internal/buildreads). Sources are
// left unsaid: the planner then reads the module's whole directory, over-planning and never under-planning, and a
// fingerprint that did not change moves nothing (R4e A9).
// left unsaid but for a module whose every artifact names its paths: the planner then reads the module's whole
// directory, over-planning and never under-planning, and a fingerprint that did not change moves nothing (R4e A9).
// **Its build source where its every artifact names its paths** (novox/hq ADR 0499 §1, §5; issue 1758, B1): the
// planner reads a merge's changed files against those paths and the manifest alone, so a change elsewhere in the
// module's directory touches nothing it builds from.
if own := pathsBuildSource(manifest, keyPath(t.Trunk)); own != nil {
kept.Sources = []inventory.BuildSource{{Paths: own}}
}
for _, c := range buildreads.Of(manifest) {
kept.Read = append(kept.Read, inventory.ReadRepository{Repository: c.Repository, Ref: c.Ref})
}
@@ -422,3 +428,33 @@ func openBuilderKeys() (builderkeys.Reader, func(), error) {
}
return builderkeys.Reader{JS: api}, js.Close, nil
}
// pathsBuildSource is the build source of a module whose every artifact names the paths it is built from (ADR 0499
// §1): its manifest and each path, relative to its repository's root, a path read as a file or everything below a
// directory of that name (`<path>/**` holds both). Nil when an artifact names none: the module reads its directory.
func pathsBuildSource(m catalogue.Manifest, dir string) []string {
if m.Build == nil || len(m.Build.Artifacts) == 0 {
return nil
}
in := func(p string) string {
if dir == "" {
return p
}
return dir + "/" + p
}
seen := map[string]bool{}
out := []string{in("module.json")}
for _, a := range m.Build.Artifacts {
if len(a.Paths) == 0 {
return nil
}
for _, p := range a.Paths {
entry := in(strings.Trim(path.Clean(p), "/")) + "/**"
if !seen[entry] {
seen[entry] = true
out = append(out, entry)
}
}
}
return out
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"github.com/novox/mesh-controller/internal/inventory"
)
// B1 (ADR 0499 §1, §5; issue 1758): a module whose every artifact names the paths it is built from is read from those
// paths and its manifest alone. mesh-sdk's TypeScript package at its repository's root: a change to its go/ touches no
// module, one to its src/ or its package.json touches it. A module with an artifact naming no paths reads its directory.
func TestAModuleNamingItsPathsIsReadFromThemAlone(t *testing.T) {
m, err := catalogue.ParseManifest([]byte(`{"module":"mesh-sdk","version":"1","build":{"artifacts":[{"name":"lib",
"kind":"package","language":"typescript","paths":["src","package.json","package-lock.json","tsconfig.json"]}]},
"resources":[]}`))
if err != nil {
t.Fatal(err)
}
own := pathsBuildSource(m, "")
read := inventory.WithBuildSources(nil, []inventory.BuildSource{{Paths: own}})
e := inventory.Entry{Manifest: m, Source: inventory.Source{Repository: "novox/mesh-sdk", Path: ""}}
for file, reads := range map[string]bool{
"go/delivery/builder.go": false, "go/go.mod": false, "README.md": false,
"src/index.ts": true, "src/deep/x.ts": true, "package.json": true, "module.json": true,
} {
if got := readsFile(e, read, file); got != reads {
t.Errorf("%s: read %v, want %v", file, got, reads)
}
}
// In a module's directory, the paths are under it.
if got := pathsBuildSource(m, "modules/sdk"); got[0] != "modules/sdk/module.json" || got[1] != "modules/sdk/src/**" {
t.Errorf("paths under a directory: %v", got)
}
// An artifact naming no paths: the whole directory, as before.
m.Build.Artifacts = append(m.Build.Artifacts, catalogue.Artifact{Name: "files", Kind: "archive", From: "files"})
if got := pathsBuildSource(m, ""); got != nil {
t.Errorf("a module with an artifact naming no paths is narrowed to %v", got)
}
}