Keep each artifact's named paths in the modules value, so the checker needs no manifest to see a change touch them (issue 1758, ADR 0499, lead decision L8)

This commit is contained in:
2026-10-22 18:00:24 +02:00
parent fd2ae83431
commit 9cb4d59093
2 changed files with 49 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
package delivery
import (
"strings"
"testing"
)
// The indexer's modules value carries each artifact's named paths (novox/hq ADR 0499; the lead's decision L8 of R4e):
// the checker tells from it whether a change touches lib's paths without reading a manifest. An older value has none.
func TestAModulesValueCarriesEachArtifactsNamedPaths(t *testing.T) {
m := Module{FP2: SourcePrefix2 + strings.Repeat("a", 64), Repository: "novox/mesh-sdk", Commit: "c",
ArtifactPaths: map[string][]string{"lib": {"package-lock.json", "package.json", "src", "test", "tsconfig.json"}}}
data, err := EncodeModule(m)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(data), `"paths":{"lib":[`) {
t.Fatalf("the value carries no paths: %s", data)
}
back, err := DecodeModule(data)
if err != nil || len(back.ArtifactPaths["lib"]) != 5 || back.ArtifactPaths["lib"][2] != "src" {
t.Fatalf("decoded %+v, %v", back.ArtifactPaths, err)
}
if old, err := DecodeModule([]byte(`{"v":1,"fp":"src1:x","repository":"a/b","commit":"c"}`)); err != nil || old.ArtifactPaths != nil {
t.Fatalf("an older value: %+v, %v", old, err)
}
for _, bad := range []map[string][]string{{"": {"src"}}, {"lib": {}}, {"lib": {"../x"}}, {"lib": {""}}} {
m.ArtifactPaths = bad
if _, err := EncodeModule(m); err == nil {
t.Errorf("%v was accepted", bad)
}
}
}
+16
View File
@@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"path"
"reflect"
"sort"
"strings"
@@ -399,6 +400,11 @@ type Module struct {
// builds the fingerprint only with these. Empty when FP2 is empty or the module has no toolchain; a value written
// before has none (issue 1615).
Toolchains map[string]ToolchainUsed `json:"toolchains,omitempty"`
// ArtifactPaths are, of each artifact that names the paths it is built from, those paths as its manifest names
// them, by artifact (novox/hq ADR 0499 clause 1): the indexer fills them from each catalogue.Artifact.Paths it reads,
// so the checker tells whether a change touches an artifact's build source without reading a manifest (the lead's
// decision L8 of R4e). Absent for an artifact that names none, and in a value written before.
ArtifactPaths map[string][]string `json:"paths,omitempty"`
}
// RootPath is Module.Path of a module whose manifest is its repository's root.
@@ -459,6 +465,16 @@ func (m Module) check() error {
if m.Inputs2 != nil && m.Manifest == "" {
return fmt.Errorf("delivery: a module keeps new-form inputs but has no manifest digest")
}
for artifact, paths := range m.ArtifactPaths {
if artifact == "" || len(paths) == 0 {
return fmt.Errorf("delivery: the module's named paths of the artifact %q are empty or name no artifact", artifact)
}
for _, p := range paths {
if p == "" || p != path.Clean(p) || strings.HasPrefix(p, "/") || p == ".." || strings.HasPrefix(p, "../") {
return fmt.Errorf("delivery: the artifact %q names the path %q, not a clean path inside the module's directory", artifact, p)
}
}
}
if m.Inputs2 != nil {
if err := checkNamed(m.Inputs2.Tree, m.Manifest, m.Inputs2.Paths, "the module's kept inputs"); err != nil {
return err