Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bca305d198 | ||
|
|
4bff5f408f | ||
|
|
0df4fd9480 | ||
|
|
cb17d59745 |
@@ -3,7 +3,7 @@ module mesh-delivery-builder
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0 h1:q6ywchpTM9fwaJ1UNwDSuOjb5FYDFcsaeiTrKoB4scc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -68,7 +68,7 @@ func (b *Builder) considerCandidate(ctx context.Context, c delivery.Candidate) {
|
||||
continue
|
||||
}
|
||||
m := delivery.Module{Repository: p.Repository, Commit: p.Head.Head, FP2: cm.FP2, Manifest: cm.Manifest, Path: trunk.Path,
|
||||
Toolchains: b.agreedFor(ctx, cm.Manifest)}
|
||||
Toolchains: b.agreedFor(ctx, cm.Manifest), Packages: cm.Packages} // the group's package heads (ADR 0573 §4)
|
||||
if len(cm.Paths) > 0 {
|
||||
// The named paths' objects the preview gave it: the fetch names the paths (runLane, ADR 0499 clause 4).
|
||||
m.Inputs2 = &delivery.KeptInputs2{Paths: cm.Paths}
|
||||
|
||||
@@ -808,6 +808,11 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m del
|
||||
job.Fetch = req.Args()
|
||||
}
|
||||
ask := delivery.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: language, Asker: delivery.BuilderModule, At: time.Now(), Build: job}
|
||||
if language == delivery.AskGo && job.Fetch != nil && len(m.Packages) > 0 {
|
||||
// The versions of the mesh packages the fingerprint was computed with (novox/hq ADR 0573 §2): the build module writes
|
||||
// them into the workspace, whatever the repository's go.mod names.
|
||||
ask.Packages = maps.Clone(m.Packages)
|
||||
}
|
||||
if id := asked.resume(language); id != "" {
|
||||
ask.ID = id // put before this builder started again: the stream holds it, or a node already ran it
|
||||
if b.Held != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -300,7 +301,14 @@ func TestABundleAndAnArchiveAreBuiltOnOneNodeAndDone(t *testing.T) { // 1607/AC7
|
||||
func TestATrunkFingerprintIsOneAskOnTheDeliveryStream(t *testing.T) {
|
||||
r := newRig(t, goBundle)
|
||||
r.asks.Answer = nil // no node has pulled it yet
|
||||
r.b.Module(context.Background(), "m", moduleValue(t, "modules/m"))
|
||||
// Issue 1585 (ADR 0573 §2): the ask carries the package versions the fingerprint was computed with.
|
||||
sdk := delivery.PackageVersions{"git.novox.be/novox/mesh-sdk/go": hex40('5')}
|
||||
data, err := delivery.EncodeModule(delivery.Module{Repository: "novox/mesh-catalog", Commit: "7a74cee83f88e6585a5511c3cc53bdf0f5303d20",
|
||||
FP2: fp1, Manifest: mdigest('a'), Path: "modules/m", Toolchains: agreedToolchains, Packages: sdk})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.b.Module(context.Background(), "m", data)
|
||||
var asks []delivery.StepAsk
|
||||
for deadline := time.Now().Add(2 * time.Second); len(asks) == 0; time.Sleep(time.Millisecond) {
|
||||
if time.Now().After(deadline) {
|
||||
@@ -312,6 +320,9 @@ func TestATrunkFingerprintIsOneAskOnTheDeliveryStream(t *testing.T) {
|
||||
if len(asks) != 1 || a.Kind != delivery.AskDelivery || a.Language != delivery.AskGo || a.Build == nil || a.Build.FP != fp1 {
|
||||
t.Fatalf("asks = %+v, want one Go delivery ask of the fingerprint", asks)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Packages, sdk) {
|
||||
t.Errorf("the ask's packages = %v, want %v", a.Packages, sdk)
|
||||
}
|
||||
if v := valueOf(t, r.st, r.key); v.State != delivery.BuildBuilding || v.Asks[delivery.AskGo] != a.ID {
|
||||
t.Fatalf("the key = %+v, want building naming the ask %s", v, a.ID)
|
||||
}
|
||||
|
||||
@@ -13,16 +13,9 @@ import (
|
||||
"mesh-delivery-checker/internal/check"
|
||||
)
|
||||
|
||||
// The forge's tools the checker reads with (mesh-catalog modules/gitea): gitea_get_file answers {file: {content}}, the text
|
||||
// of a file at a ref, and gitea_compare answers {total_commits, commits}, what head holds that base does not. The checker
|
||||
// writes nothing to the forge: the forge bridge sets the statuses (ADR 0484 clause 2).
|
||||
const (
|
||||
getFileTool = "gitea.gitea_get_file"
|
||||
compareTool = "gitea.gitea_compare"
|
||||
commitsTool = "gitea.gitea_commits"
|
||||
// gitea_list_branches answers {branches: [{name, commit}]}, every branch with the commit it points at.
|
||||
branchesTool = "gitea.gitea_list_branches"
|
||||
)
|
||||
// The forge's tool the checker reads with (mesh-catalog modules/gitea): gitea_get_file answers {file: {content}}, the text
|
||||
// of a file at a ref. The checker writes nothing to the forge: the forge bridge sets the statuses (ADR 0484 clause 2).
|
||||
const getFileTool = "gitea.gitea_get_file"
|
||||
|
||||
// notFoundStatus is the 404 of the forge's error, `Gitea API <path>: <status> <body>`.
|
||||
var notFoundStatus = regexp.MustCompile(`: 404( |$)`)
|
||||
@@ -69,61 +62,3 @@ func (f forgeReads) File(_ context.Context, repository, path, ref string) (strin
|
||||
}
|
||||
return *a.File.Content, nil
|
||||
}
|
||||
|
||||
// OnTrunk says whether the commit is already on the repository's main: main holds it when the commit holds nothing main does
|
||||
// not.
|
||||
func (f forgeReads) OnTrunk(_ context.Context, repository, commit string) (bool, error) {
|
||||
owner, repo := ownerRepo(repository)
|
||||
raw, err := f.ask(compareTool, map[string]string{"owner": owner, "repo": repo, "base": "main", "head": commit})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var a struct {
|
||||
Total *int `json:"total_commits"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &a); err != nil || a.Total == nil {
|
||||
return false, fmt.Errorf("the answer of %s is not {total_commits}: %v", compareTool, err)
|
||||
}
|
||||
return *a.Total == 0, nil
|
||||
}
|
||||
|
||||
// FullSHA is the whole sha of a commit named by its first digits: the newest commit of the history at that ref.
|
||||
func (f forgeReads) FullSHA(_ context.Context, repository, commit string) (string, error) {
|
||||
owner, repo := ownerRepo(repository)
|
||||
raw, err := f.ask(commitsTool, map[string]any{"owner": owner, "repo": repo, "ref": commit, "limit": 1})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var a struct {
|
||||
Commits []struct {
|
||||
SHA string `json:"sha"`
|
||||
} `json:"commits"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &a); err != nil || len(a.Commits) == 0 || len(a.Commits[0].SHA) != 40 || !strings.HasPrefix(a.Commits[0].SHA, commit) {
|
||||
return "", fmt.Errorf("the answer of %s for %s is not a commit that starts with it: %v", commitsTool, commit, err)
|
||||
}
|
||||
return a.Commits[0].SHA, nil
|
||||
}
|
||||
|
||||
// BranchHeads is the commit each branch of the repository points at.
|
||||
func (f forgeReads) BranchHeads(_ context.Context, repository string) (map[string]string, error) {
|
||||
owner, repo := ownerRepo(repository)
|
||||
raw, err := f.ask(branchesTool, map[string]string{"owner": owner, "repo": repo})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var a struct {
|
||||
Branches *[]struct {
|
||||
Name string `json:"name"`
|
||||
Commit string `json:"commit"`
|
||||
} `json:"branches"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &a); err != nil || a.Branches == nil {
|
||||
return nil, fmt.Errorf("the answer of %s is not {branches: [{name, commit}]}: %v", branchesTool, err)
|
||||
}
|
||||
heads := map[string]string{}
|
||||
for _, b := range *a.Branches {
|
||||
heads[b.Name] = b.Commit
|
||||
}
|
||||
return heads, nil
|
||||
}
|
||||
|
||||
@@ -29,49 +29,19 @@ func TestTheForgeReadsParseWhatTheForgeAnswered(t *testing.T) { // 1624/AC4
|
||||
f := forgeReads{call: func(key string, body any) (json.RawMessage, error) {
|
||||
b, _ := json.Marshal(body)
|
||||
asked = append(asked, key+" "+string(b))
|
||||
switch {
|
||||
case key == getFileTool:
|
||||
return capturedAnswer(t, "forge-get-file-go-mod"), nil
|
||||
case strings.Contains(string(b), "cb54756c3865"):
|
||||
return capturedAnswer(t, "forge-compare-trunk"), nil
|
||||
}
|
||||
return capturedAnswer(t, "forge-compare-branch"), nil
|
||||
return capturedAnswer(t, "forge-get-file-go-mod"), nil
|
||||
}}
|
||||
text, err := f.File(context.Background(), "novox/mesh-delivery", "modules/mesh-delivery-builder/go.mod", "main")
|
||||
if err != nil || !strings.Contains(text, "git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021100209-cb54756c3865") {
|
||||
t.Errorf("file = %q, %v", text, err)
|
||||
}
|
||||
if on, err := f.OnTrunk(context.Background(), "novox/mesh-sdk", "cb54756c3865"); err != nil || !on {
|
||||
t.Errorf("a commit on the trunk: %v, %v", on, err)
|
||||
}
|
||||
if on, err := f.OnTrunk(context.Background(), "novox/mesh-sdk", "9aaa5c80ac5c"); err != nil || on {
|
||||
t.Errorf("a commit of the branch: %v, %v", on, err)
|
||||
}
|
||||
if len(asked) != 3 || !strings.Contains(asked[0], `"owner":"novox"`) || !strings.Contains(asked[2], `"base":"main"`) {
|
||||
if len(asked) != 1 || !strings.Contains(asked[0], `"owner":"novox"`) {
|
||||
t.Errorf("asked %v", asked)
|
||||
}
|
||||
bad := forgeReads{call: func(string, any) (json.RawMessage, error) { return json.RawMessage(`{}`), nil }}
|
||||
if _, err := bad.File(context.Background(), "novox/x", "go.mod", "main"); err == nil {
|
||||
t.Error("an answer with no file was taken")
|
||||
}
|
||||
if _, err := bad.OnTrunk(context.Background(), "novox/x", "abc"); err == nil {
|
||||
t.Error("an answer with no total was taken")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheForgeNamesTheWholeShaOfAShortCommit(t *testing.T) { // review D2
|
||||
f := forgeReads{call: func(key string, body any) (json.RawMessage, error) {
|
||||
if key != commitsTool {
|
||||
t.Errorf("called %s", key)
|
||||
}
|
||||
return capturedAnswer(t, "forge-commits"), nil
|
||||
}}
|
||||
if sha, err := f.FullSHA(context.Background(), "novox/mesh-sdk", "cb54756c3865"); err != nil || sha != "cb54756c3865df5ac9c0dc0481ad9ebe2bf013a1" {
|
||||
t.Errorf("sha = %q, %v", sha, err)
|
||||
}
|
||||
if _, err := f.FullSHA(context.Background(), "novox/mesh-sdk", "deadbeefdead"); err == nil {
|
||||
t.Error("a commit that does not start with the pin was taken")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAFileTheRefDoesNotHoldIsNotFoundAndOtherFailuresAreNot(t *testing.T) { // 1845/AC1, AC4
|
||||
@@ -96,10 +66,4 @@ func TestAForgeReadThatReachesNobodyIsUnheard(t *testing.T) { // 1624 C7: "nats:
|
||||
if !errors.As(err, &u) {
|
||||
t.Errorf("err = %v, want it marked as reaching nobody", err)
|
||||
}
|
||||
if _, err := f.OnTrunk(context.Background(), "novox/mesh-sdk", "abc"); !errors.As(err, &u) {
|
||||
t.Errorf("compare: %v", err)
|
||||
}
|
||||
if _, err := f.FullSHA(context.Background(), "novox/mesh-sdk", "abc"); !errors.As(err, &u) {
|
||||
t.Errorf("commits: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,18 +687,17 @@ func TestACheckLeftRunningIsTakenOverWhenTheCheckerStarts(t *testing.T) { // N6/
|
||||
}
|
||||
}
|
||||
|
||||
// TestAMemberNamingAMeshSDKCommitOffTheTrunkFailsNamingTheCommit: the go.mod the forge answers names a commit that main does not
|
||||
// hold; the verdict fails naming the member and the commit and no test runs (ADR 0484 clause 9).
|
||||
func TestAMemberNamingAMeshSDKCommitOffTheTrunkFailsNamingTheCommit(t *testing.T) { // 1624/AC2, 1929/AC3
|
||||
// TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused: the go.mod the forge answers names a commit that main does
|
||||
// not hold; the check is not refused for it, since the build takes the latest published mesh-sdk whatever go.mod names
|
||||
// (novox/hq ADR 0573 §2, §4; issue 1585 S14, which retires issue 1624's pin check).
|
||||
func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.T) { // 1624/AC2 as ADR 0573 §4 changes it
|
||||
b := startBed(t)
|
||||
b.stalePin.Store(true)
|
||||
key := b.candidate(t, "feat/x", pull(repo, 7, head1))
|
||||
b.start(t, nil)
|
||||
v := b.verdict(t, key, ended)
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") || !strings.Contains(v.Failed.Words, repo+"#7") {
|
||||
if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
// 1929/AC3: no assertion that no test ran: one ask runs fetch, digest and tests (ADR 0525 §3).
|
||||
}
|
||||
|
||||
// triesStore is the tries bucket as the tools write it.
|
||||
|
||||
@@ -69,7 +69,7 @@ func newJudge(d judgeDeps) check.Judge {
|
||||
nodes = d.nodes
|
||||
}
|
||||
return &check.Examiner{Nodes: nodes, Compositions: d.compositions, Asks: d.asks, Toolchains: d.tools,
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Forge: d.forge, Runs: d.repos, Builds: d.builds, Announce: d.announce, Trunks: d.trunks, Logf: d.logf}
|
||||
Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.repos, Forge: d.forge, Runs: d.repos, Builds: d.builds, Announce: d.announce, Trunks: d.trunks, Logf: d.logf}
|
||||
}
|
||||
|
||||
// manifestKV reads a manifest by its digest.
|
||||
@@ -92,6 +92,7 @@ type repoModules struct {
|
||||
repoOf map[string]string // by module: its repository
|
||||
commit map[string]string // by module: the commit its record was computed at (the commit the mesh runs)
|
||||
typed map[string]string // by module: its manifest digest
|
||||
uses map[string][]string // by module: the mesh packages it uses (Inputs2.Packages, ADR 0573 §2)
|
||||
}
|
||||
|
||||
// Put takes a change of the indexer's modules bucket in: a module's repository and the languages its manifest names.
|
||||
@@ -100,6 +101,7 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted
|
||||
if r.repoOf == nil {
|
||||
r.repoOf, r.typed, r.langs, r.commit = map[string]string{}, map[string]string{}, map[string]map[string]bool{}, map[string]string{}
|
||||
r.tsPkgs, r.path, r.named = map[string][]string{}, map[string]string{}, map[string]map[string][]string{}
|
||||
r.uses = map[string][]string{}
|
||||
}
|
||||
if deleted {
|
||||
delete(r.repoOf, key)
|
||||
@@ -107,6 +109,7 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted
|
||||
delete(r.commit, key)
|
||||
delete(r.path, key)
|
||||
delete(r.named, key)
|
||||
delete(r.uses, key)
|
||||
r.mu.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -121,6 +124,10 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted
|
||||
r.commit[key] = m.Commit
|
||||
r.path[key] = m.Path
|
||||
r.named[key] = m.ArtifactPaths
|
||||
r.uses[key] = nil
|
||||
if m.Inputs2 != nil {
|
||||
r.uses[key] = m.Inputs2.Packages
|
||||
}
|
||||
_, known := r.langs[m.Manifest]
|
||||
r.mu.Unlock()
|
||||
if m.Manifest == "" || known {
|
||||
@@ -152,6 +159,28 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// LatestPackages is the latest published version of each mesh package a module of the mesh uses, and of mesh-sdk's Go
|
||||
// module always (novox/hq ADR 0573 §2): the trunk merge of the module whose directory holds it, as the indexer's modules
|
||||
// say. A package no module holds is left out: a check builds it at the version go.mod names.
|
||||
func (r *repoModules) LatestPackages() delivery.PackageVersions {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
used := map[string]bool{check.SDKModulePath: true}
|
||||
held := map[string]delivery.Module{}
|
||||
for module, repo := range r.repoOf {
|
||||
for _, p := range r.uses[module] {
|
||||
used[p] = true
|
||||
}
|
||||
held[module] = delivery.Module{Repository: repo, Path: r.path[module], Commit: r.commit[module]}
|
||||
}
|
||||
pkgs := make([]string, 0, len(used))
|
||||
for p := range used {
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
versions, _ := delivery.PackageVersionsOf(pkgs, held)
|
||||
return versions
|
||||
}
|
||||
|
||||
// PackagePaths are the paths, relative to the repository, that the TypeScript package artifacts of a repository's modules
|
||||
// are built from, sorted: the named paths the indexer keeps in each module's value (delivery.Module.ArtifactPaths; only the
|
||||
// indexer reads them from a manifest, the lead's decision L8), under the module's directory. None for a module whose value
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -112,6 +113,20 @@ func TestTheTypeScriptPackagesPathsComeFromTheIndexersModulesNotTheManifest(t *t
|
||||
if got := r.PackagePaths("novox/mesh-delivery"); len(got) != 0 {
|
||||
t.Errorf("another repository: %v", got)
|
||||
}
|
||||
// Issue 1585 (ADR 0573 §2): the latest published version of mesh-sdk's Go module and of every package a module uses is
|
||||
// the commit of the module that holds it; a package no module holds is left out.
|
||||
put(".", nil)
|
||||
const contract = "git.novox.be/novox/mesh-controller/modules/route-proxy/contract"
|
||||
user, err := delivery.EncodeModule(delivery.Module{Repository: "novox/mesh-catalog", Commit: strings.Repeat("b", 40), FP: "src1:" + strings.Repeat("b", 64),
|
||||
Manifest: delivery.ManifestDigest([]byte(sdkManifest)), Path: "modules/x",
|
||||
Inputs2: &delivery.KeptInputs2{Tree: "t", Packages: []string{contract, "git.novox.be/novox/mesh-sdk/go"}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.Put(context.Background(), "x", user, false)
|
||||
if got := r.LatestPackages(); !reflect.DeepEqual(got, delivery.PackageVersions{"git.novox.be/novox/mesh-sdk/go": strings.Repeat("a", 40)}) {
|
||||
t.Errorf("latest packages = %v, want mesh-sdk at its module's commit and no unheld contract", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A manifest whose config value falls back to a predefined key (ADR 0348 §7) is read, not logged as unreadable for the
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-checker
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0 h1:q6ywchpTM9fwaJ1UNwDSuOjb5FYDFcsaeiTrKoB4scc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -77,20 +77,6 @@ func (j *Examiner) besideFor(ctx context.Context, c *Case, p Pull) ([]BesideRef,
|
||||
// running commit puts the verdict out of date; at the trunk, a trunk move does (Checker.besideNow).
|
||||
out = append(out, BesideRef{Repository: repo, Commit: commit, atTrunk: atTrunk, runs: !atTrunk})
|
||||
}
|
||||
case repo == SDKRepository && p.Repository == controllerRepo && j.Forge != nil:
|
||||
text, err := j.Forge.File(ctx, p.Repository, "go.mod", p.Head)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go.mod of %s#%d cannot be read to place the mesh-sdk its tests read: %w", p.Repository, p.Number, err)
|
||||
}
|
||||
pin, ok := SDKPin(text)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("go.mod of %s#%d names no mesh-sdk commit, so the mesh-sdk its tests read cannot be placed", p.Repository, p.Number)
|
||||
}
|
||||
full, err := j.Forge.FullSHA(ctx, SDKRepository, pin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mesh-sdk %s, which %s#%d pins, cannot be found: %w", pin, p.Repository, p.Number, err)
|
||||
}
|
||||
out = append(out, BesideRef{Repository: repo, Commit: full})
|
||||
default:
|
||||
if member := c.member(repo); member != nil {
|
||||
out = append(out, BesideRef{Repository: repo, Commit: member.Head, Ref: refOf(member.Number)})
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Issue 1820 AC5: a read of the forge that nobody answers is the forge's failure, not a build node's. (AC1-AC4 moved a
|
||||
// step to another node; since R4g an ask whose node stops is delivered again by its stream: ADR 0525 §6.)
|
||||
func TestAPinReadThatNobodyAnswersIsTheForgesFailureNotABuildNodes(t *testing.T) { // 1820/AC5
|
||||
b := groupBench(t, "cb54756c3865")
|
||||
b.ex.Forge.(*fakeForge).err = fmt.Errorf("gitea.gitea_get_file: %w", nats.ErrNoResponders)
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
||||
if v.State != delivery.VerdictFail || v.Failed.Machine != "forge" {
|
||||
t.Errorf("verdict = %+v, want a failure at the forge: no build node was to blame", v)
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,14 @@ type Examiner struct {
|
||||
Modules BuildModules
|
||||
// Runs says which commit the mesh runs of a module (the indexer's modules); nil knows none.
|
||||
Runs interface{ CommitOf(module string) string }
|
||||
// Forge reads the go.mod files of the Go members and says whether a mesh-sdk commit is on its trunk (ADR 0484 clause 9);
|
||||
// nil checks no pin.
|
||||
// Forge reads the forge's files for the siblings a repository's tests read; nil reads none.
|
||||
Forge Forge
|
||||
// Published names the latest published version of every mesh package a module of the mesh uses, by package, from the
|
||||
// indexer's modules (novox/hq ADR 0573 §2): a check builds against those, or against a member's head for a package
|
||||
// its group publishes (§4), whatever go.mod names. Nil names none, and go.mod's versions are built.
|
||||
Published interface {
|
||||
LatestPackages() delivery.PackageVersions
|
||||
}
|
||||
// Builds says how the build of a fingerprint the candidate carries stands (the builder's builds bucket); nil says nothing
|
||||
// of the builds.
|
||||
Builds BuildStates
|
||||
@@ -394,8 +399,8 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict {
|
||||
// need that is busy is waited for, and short enough that a mesh where no node has it says so.
|
||||
const DefaultDeclineBound = 30 * time.Minute
|
||||
|
||||
// ask puts the target's check ask, waits for the step of the node that pulled it and judges its answer: the fetch, the
|
||||
// pins of its go.mod files, then each level's digest, kept verdicts and tests. When the answer leaves packages unmet,
|
||||
// ask puts the target's check ask, waits for the step of the node that pulled it and judges its answer: the fetch, then
|
||||
// each level's digest, kept verdicts and tests. When the answer leaves packages unmet,
|
||||
// their tests needing what that node lacks (issue 890), the passes are kept and the levels from the first such one are
|
||||
// asked again of a node that has it (placed).
|
||||
func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *delivery.Verdict {
|
||||
@@ -431,6 +436,9 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
}
|
||||
a := delivery.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: askLanguage[t.module], Asker: delivery.CheckerModule,
|
||||
At: st.j.now().UTC(), Check: job, Needs: needs}
|
||||
if a.Language == delivery.AskGo {
|
||||
a.Packages = st.packagesOf(p.Repository)
|
||||
}
|
||||
var step delivery.Step
|
||||
var firstDecline time.Time
|
||||
for {
|
||||
@@ -487,7 +495,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
st.nodes[step.Node] = true
|
||||
st.c.Progress.Set(fmt.Sprintf("judge the check of %s#%d", p.Repository, p.Number), step.Node)
|
||||
if step.State != delivery.StepPassed {
|
||||
return fail(step.Node, fmt.Sprintf("the check of %s#%d %s: %s%s", p.Repository, p.Number, step.State, step.Reason, pinHint(st.c, step.Reason)))
|
||||
return fail(step.Node, fmt.Sprintf("the check of %s#%d %s: %s", p.Repository, p.Number, step.State, step.Reason))
|
||||
}
|
||||
var answer delivery.CheckAnswer
|
||||
if err := json.Unmarshal(step.Result, &answer); err != nil {
|
||||
@@ -506,11 +514,8 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l
|
||||
if !changesBuildGo(p.Repository, t.changed) {
|
||||
t.runner = "" // the build module used no runner: the merge does not change it
|
||||
}
|
||||
if !placed { // the placed ask fetched the same merge: its preview and pins were read from the first
|
||||
if !placed { // the placed ask fetched the same merge: its preview was read from the first
|
||||
*merged = append(*merged, t.fetched)
|
||||
if v := st.checkPinsOf(ctx, t); v != nil {
|
||||
return v
|
||||
}
|
||||
if v := st.checkBinariesOf(t); v != nil {
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ package check
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"maps"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The pin of mesh-sdk in a Go member's go.mod (novox/hq ADR 0484 clause 9, ADR 0482 clause 2, issue 1624): a delivery group
|
||||
// has one verdict, and the mesh-sdk its Go members are built and tested with is the group's: a pin to the head of the group's
|
||||
// mesh-sdk pull request, or to a commit already on mesh-sdk's trunk. Any other commit is a stale or a foreign pin, and the
|
||||
// group fails naming the member and the commit.
|
||||
// **A check builds against the group's own package heads, not against what a member names** (novox/hq ADR 0573 §2, §4;
|
||||
// issue 1585). Every Go check ask carries the version of each mesh package to build against (delivery.StepAsk.Packages):
|
||||
// the head of the group's member that publishes it, else its latest published version as the indexer holds it. The build
|
||||
// module writes them into the workspace, whatever version or commit a member's go.mod names, so no member is refused
|
||||
// for naming no version of the group's package, an older head of it, a merge commit or a commit on no pull request: that
|
||||
// refusal (issue 1624's pin check) was a fault of the check (§4), and is gone.
|
||||
|
||||
// SDKRepository is the repository of mesh-sdk, and SDKModulePath its Go module.
|
||||
const (
|
||||
@@ -21,142 +21,47 @@ const (
|
||||
SDKModulePath = "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// Issue1148 is why a pin that only a head carries fails the check.
|
||||
const Issue1148 = "issue 1148"
|
||||
|
||||
// ErrNotFound is what Forge.File wraps when the ref holds no such file (the forge answered 404): a file the ref does not hold,
|
||||
// told from a forge that could not be read (issue 1845).
|
||||
var ErrNotFound = errors.New("the ref holds no such file")
|
||||
|
||||
// Forge reads a file of a repository at a ref, and says whether a commit is on a repository's trunk.
|
||||
// Forge reads a file of a repository at a ref.
|
||||
type Forge interface {
|
||||
// File is the text of a file at a ref (a branch, a tag or a commit).
|
||||
File(ctx context.Context, repository, path, ref string) (string, error)
|
||||
// OnTrunk says whether commit (a full or a 12-digit sha) is already merged into the trunk of the repository: it holds no
|
||||
// commit the trunk does not.
|
||||
OnTrunk(ctx context.Context, repository, commit string) (bool, error)
|
||||
// FullSHA is the 40-digit sha of a commit named by its first 12 digits.
|
||||
FullSHA(ctx context.Context, repository, commit string) (string, error)
|
||||
// BranchHeads is the commit every branch of a repository points at, by branch name (issue 1921).
|
||||
BranchHeads(ctx context.Context, repository string) (map[string]string, error)
|
||||
}
|
||||
|
||||
var pseudoVersion = regexp.MustCompile(`-([0-9a-f]{12})$`)
|
||||
|
||||
// SDKPin is the commit (12 hex digits) a go.mod names for mesh-sdk by a pseudo-version, or none for a go.mod that does not
|
||||
// require mesh-sdk or requires a version that is no pseudo-version (a tag is a release the SDK itself names).
|
||||
func SDKPin(goMod string) (commit string, ok bool) {
|
||||
for _, line := range strings.Split(goMod, "\n") {
|
||||
f := strings.Fields(strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "require")))
|
||||
if len(f) >= 2 && f[0] == SDKModulePath {
|
||||
if m := pseudoVersion.FindStringSubmatch(f[1]); m != nil {
|
||||
return m[1], true
|
||||
}
|
||||
return "", false
|
||||
// packagesOf is the version of each mesh package a Go check of repository builds against (ADR 0573 §2, §4): the latest
|
||||
// published version of each package the mesh's modules use (Published), and for a package one of the case's pull
|
||||
// requests publishes, that pull request's head (mesh-sdk's Go module whenever mesh-sdk is a member). A package of
|
||||
// repository itself comes with the tree the check fetched, and is left out. Nil for none.
|
||||
func (st *state) packagesOf(repository string) delivery.PackageVersions {
|
||||
versions := delivery.PackageVersions{}
|
||||
if st.j.Published != nil {
|
||||
maps.Copy(versions, st.j.Published.LatestPackages())
|
||||
}
|
||||
if st.c.member(SDKRepository) != nil {
|
||||
if _, named := versions[SDKModulePath]; !named {
|
||||
versions[SDKModulePath] = ""
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// checkPinsOf refuses a Go member whose go.mod names a mesh-sdk commit that is neither on mesh-sdk's trunk nor the head of the
|
||||
// group's mesh-sdk pull request. It reads each go.mod the fetch named from the forge: the one the check builds, which is the
|
||||
// head's when the head holds the file and the trunk's when it does not, since a module added on the trunk after the branch was
|
||||
// cut is in the merge and not at the head (issue 1845). It answers nil when every pin is the group's.
|
||||
func (st *state) checkPinsOf(ctx context.Context, t *target) verdictPtr {
|
||||
if st.j.Forge == nil {
|
||||
for pkg := range versions {
|
||||
repo, _, ok := delivery.PackageRepository(pkg)
|
||||
if !ok || delivery.SameRepository(repo, repository) {
|
||||
delete(versions, pkg)
|
||||
continue
|
||||
}
|
||||
if m := st.c.member(repo); m != nil && len(m.Head) == 40 {
|
||||
versions[pkg] = m.Head
|
||||
}
|
||||
if versions[pkg] == "" {
|
||||
delete(versions, pkg)
|
||||
}
|
||||
}
|
||||
if len(versions) == 0 {
|
||||
return nil
|
||||
}
|
||||
sdk := st.c.member(SDKRepository)
|
||||
if t.module != goModule || t.pull.Repository == SDKRepository {
|
||||
return nil
|
||||
}
|
||||
{
|
||||
for _, dir := range t.fetched.Modules {
|
||||
file := path.Join(dir, "go.mod")
|
||||
st.c.Progress.Set(fmt.Sprintf("read %s of %s#%d", file, t.pull.Repository, t.pull.Number), t.node)
|
||||
text, err := st.j.Forge.File(ctx, t.pull.Repository, file, t.pull.Head)
|
||||
if errors.Is(err, ErrNotFound) && t.pull.Trunk != "" {
|
||||
// Only a file the head does not hold is read at the trunk; any other failure fails the check as it did.
|
||||
text, err = st.j.Forge.File(ctx, t.pull.Repository, file, t.pull.Trunk)
|
||||
}
|
||||
if err != nil {
|
||||
return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: %s cannot be read to check its mesh-sdk pin: %v%s", t.pull.Repository, t.pull.Number, file, err, unheardWords(err)))
|
||||
}
|
||||
pin, ok := SDKPin(text)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if sdk != nil && strings.HasPrefix(sdk.Head, pin) {
|
||||
continue // the group's own head
|
||||
}
|
||||
onTrunk, err := st.j.Forge.OnTrunk(ctx, SDKRepository, pin)
|
||||
if err != nil {
|
||||
return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: whether mesh-sdk %s is on its trunk cannot be told: %v%s", t.pull.Repository, t.pull.Number, pin, err, unheardWords(err)))
|
||||
}
|
||||
if onTrunk {
|
||||
continue
|
||||
}
|
||||
if st.c.Try != nil {
|
||||
// A try only (issue 1921): a repository tried on a branch that mesh-sdk shares by name (a milestone's branch across
|
||||
// repositories) is built with that branch's mesh-sdk head, which no group pull request holds before the pull requests exist.
|
||||
branch, err := st.sameBranchPin(ctx, t, pin)
|
||||
if err != nil {
|
||||
return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: whether mesh-sdk %s is the head of a branch cannot be told: %v%s", t.pull.Repository, t.pull.Number, pin, err, unheardWords(err)))
|
||||
}
|
||||
if branch != "" {
|
||||
st.c.note(fmt.Sprintf("pin accepted: head of mesh-sdk %s", branch))
|
||||
continue
|
||||
}
|
||||
}
|
||||
nor := "the group has no mesh-sdk pull request"
|
||||
if sdk != nil {
|
||||
nor = "the head of the group's mesh-sdk pull request is " + short12(sdk.Head)
|
||||
}
|
||||
return failed(t.node, fmt.Sprintf("%s#%d: %s names mesh-sdk %s, which is neither on mesh-sdk's trunk nor the group's head (%s)", t.pull.Repository, t.pull.Number, file, pin, nor))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sameBranchPin names the branch of the tried repository that points at the tried head and that mesh-sdk also has, at the
|
||||
// commit the pin names; empty when there is none.
|
||||
func (st *state) sameBranchPin(ctx context.Context, t *target, pin string) (string, error) {
|
||||
mine, err := st.j.Forge.BranchHeads(ctx, t.pull.Repository)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var same []string
|
||||
for name, head := range mine {
|
||||
if head == t.pull.Head && name != "main" {
|
||||
same = append(same, name)
|
||||
}
|
||||
}
|
||||
if len(same) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
sdk, err := st.j.Forge.BranchHeads(ctx, SDKRepository)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sort.Strings(same)
|
||||
for _, name := range same {
|
||||
if h, ok := sdk[name]; ok && strings.HasPrefix(h, pin) {
|
||||
return name, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// failureVerdict is a verdict of failure the check ends with.
|
||||
|
||||
// pinHint is added to the failure of a Go member's fetch that names mesh-sdk while the group holds a mesh-sdk pull request:
|
||||
// a pin to that pull request's head names a commit only the head carries, which go mod download cannot resolve before the
|
||||
// pull request is merged (issue 1148), so such a check fails and never passes.
|
||||
func pinHint(c *Case, fetchError string) string {
|
||||
if c.member(SDKRepository) == nil || !strings.Contains(fetchError, "mesh-sdk") {
|
||||
return ""
|
||||
}
|
||||
return " (a pin to the head of the group's mesh-sdk pull request names a commit only that head carries, which go mod download cannot resolve; " + Issue1148 + ")"
|
||||
return versions
|
||||
}
|
||||
|
||||
// unheardWords ends the words of a failed read of the forge that reached nobody: the forge was not heard (UnheardWords).
|
||||
|
||||
@@ -1,214 +1,63 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"maps"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// fakeForge answers the forge's two reads from files and commits it is given.
|
||||
type fakeForge struct {
|
||||
goMods map[string]string // `<repository> <path> <ref>` -> text
|
||||
onTrunk map[string]bool // by commit prefix
|
||||
err error
|
||||
errAt map[string]error // by ref: a read at that ref fails with it
|
||||
// branches are the branches of a repository with the commit each points at.
|
||||
branches map[string]map[string]string
|
||||
// published is the latest published version of each mesh package, as the indexer's modules say (Examiner.Published).
|
||||
type published delivery.PackageVersions
|
||||
|
||||
func (p published) LatestPackages() delivery.PackageVersions {
|
||||
return maps.Clone(delivery.PackageVersions(p))
|
||||
}
|
||||
|
||||
func (f *fakeForge) BranchHeads(_ context.Context, repository string) (map[string]string, error) {
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
return f.branches[repository], nil
|
||||
}
|
||||
|
||||
func (f *fakeForge) File(_ context.Context, repository, path, ref string) (string, error) {
|
||||
if f.err != nil {
|
||||
return "", f.err
|
||||
}
|
||||
if e := f.errAt[ref]; e != nil {
|
||||
return "", e
|
||||
}
|
||||
if s, ok := f.goMods[repository+" "+path+" "+ref]; ok {
|
||||
return s, nil
|
||||
}
|
||||
return "", fmt.Errorf("no file %s of %s at %s: %w", path, repository, ref, ErrNotFound)
|
||||
}
|
||||
|
||||
func (f *fakeForge) FullSHA(_ context.Context, _ string, commit string) (string, error) {
|
||||
if f.err != nil {
|
||||
return "", f.err
|
||||
}
|
||||
return commit + strings.Repeat("0", 40-len(commit)), nil
|
||||
}
|
||||
|
||||
func (f *fakeForge) OnTrunk(_ context.Context, _ string, commit string) (bool, error) {
|
||||
if f.err != nil {
|
||||
return false, f.err
|
||||
}
|
||||
return f.onTrunk[commit[:12]], nil
|
||||
}
|
||||
|
||||
// capturedGoMod is the go.mod the forge answered (testdata/captured/forge-get-file-go-mod.json), pinned to the commit given.
|
||||
func capturedGoMod(t *testing.T, pin string) string {
|
||||
// packagesOfFetch is the package versions the fetch of repository was handed (delivery.StepAsk.Packages, issue 1585).
|
||||
func packagesOfFetch(t *testing.T, b *bench, repository string) delivery.PackageVersions {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile(filepath.Join("testdata", "captured", "forge-get-file-go-mod.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
for _, f := range b.steps.tool("fetch") {
|
||||
if f.args["repository"] == repository {
|
||||
p, err := buildstep.PackagesArg(f.args["packages"])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
}
|
||||
var a struct{ File struct{ Content string } }
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return strings.Replace(a.File.Content, "cb54756c3865", pin, 1)
|
||||
t.Fatalf("%s was not fetched", repository)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTheSDKPinIsTheCommitOfAPseudoVersion(t *testing.T) { // 1624
|
||||
if c, ok := SDKPin(capturedGoMod(t, "cb54756c3865")); !ok || c != "cb54756c3865" {
|
||||
t.Errorf("pin = %q, %v", c, ok)
|
||||
}
|
||||
if _, ok := SDKPin("module x\n\nrequire git.novox.be/novox/mesh-sdk/go v0.2.0\n"); ok {
|
||||
t.Error("a tagged version is no commit")
|
||||
}
|
||||
if c, ok := SDKPin("module x\n\nrequire git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021100209-cb54756c3865\n"); !ok || c != "cb54756c3865" {
|
||||
t.Errorf("a one-line require: %q, %v", c, ok)
|
||||
}
|
||||
if _, ok := SDKPin("module x\n\ngo 1.26\n"); ok {
|
||||
t.Error("a go.mod that does not require mesh-sdk has a pin")
|
||||
}
|
||||
}
|
||||
|
||||
// groupBench is a group of mesh-delivery#7 and the SDK's pull request 12, mesh-delivery's go.mod at its head naming pin.
|
||||
func groupBench(t *testing.T, pin string, trunk ...string) *bench {
|
||||
// ADR 0573 §4 (issue 1585 S14): a group's Go member is built against the head of the group's mesh-sdk member, whatever its
|
||||
// go.mod names (an older head of it included, which issue 1624's pin check refused), and never refused for its pin; a
|
||||
// pull request alone is built against the latest published version of each package, and its own repository's packages
|
||||
// come with its tree.
|
||||
func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *testing.T) { // 1624/AC2, AC4 as ADR 0573 §4 changes them
|
||||
const contract = "git.novox.be/novox/mesh-delivery/contract"
|
||||
b := newBench(t)
|
||||
b.fetch.Modules = []string{"modules/m"}
|
||||
forge := &fakeForge{goMods: map[string]string{repoA + " modules/m/go.mod " + head1: capturedGoMod(t, pin)}, onTrunk: map[string]bool{}}
|
||||
for _, c := range trunk {
|
||||
forge.onTrunk[c] = true
|
||||
}
|
||||
b.ex.Forge = forge
|
||||
return b
|
||||
}
|
||||
|
||||
func TestAMemberPinnedToTheSDKMembersHeadPassesAndIsBuiltAgainstIt(t *testing.T) { // 1624/AC2, AC4
|
||||
b := groupBench(t, head2[:12])
|
||||
b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
||||
if v.State != delivery.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
var besideSDK bool
|
||||
for _, f := range b.steps.tool("fetch") {
|
||||
if f.args["repository"] == repoA {
|
||||
refs, _ := refsOf(f.args["beside"]), true
|
||||
for _, r := range refs {
|
||||
besideSDK = besideSDK || (r.Repository == repoSDK && r.Commit == head2)
|
||||
}
|
||||
}
|
||||
if got := packagesOfFetch(t, b, repoA); len(got) != 1 || got[SDKModulePath] != head2 {
|
||||
t.Errorf("the member's packages = %v, want mesh-sdk at the group's head %s alone (its own contract comes with its tree)", got, head2)
|
||||
}
|
||||
if !besideSDK {
|
||||
if s := besideOf(t, b, repoA)[repoSDK]; s.Commit != head2 {
|
||||
t.Error("mesh-delivery's tests were not given the SDK member at its head")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMemberPinnedToAnOlderHeadOfTheSDKMemberFailsNamingTheMemberAndTheCommit(t *testing.T) { // 1624/AC2, AC4
|
||||
b := groupBench(t, "9aaa5c80ac5c") // a commit of the SDK branch, not its head, not on the trunk
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
||||
f := v.Failed
|
||||
if v.State != delivery.VerdictFail || f.Machine != "shanks" || !strings.Contains(f.Words, repoA+"#7") || !strings.Contains(f.Words, "9aaa5c80ac5c") ||
|
||||
!strings.Contains(f.Words, "neither on mesh-sdk's trunk nor the group's head") || !strings.Contains(f.Words, short12(head2)) {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
b = newBench(t)
|
||||
b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA}
|
||||
if v := b.judge(t, caseOf(t, pullOf("novox/mesh-catalog", 7, head1))).Verdict; v.State != delivery.VerdictPass {
|
||||
t.Fatalf("a pull request alone: verdict = %+v", v)
|
||||
}
|
||||
// Since R4g the levels run in the member's one check ask before its go.mod pins are judged (ADR 0525 §3); the verdict is
|
||||
// still the pin's, whatever the levels said.
|
||||
}
|
||||
|
||||
func TestAMemberPinnedToACommitOnTheTrunkPasses(t *testing.T) { // 1624/AC2, AC4
|
||||
b := groupBench(t, "cb54756c3865", "cb54756c3865")
|
||||
if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict; v.State != delivery.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
// A lone pull request of a Go member is held to the trunk alone.
|
||||
b = groupBench(t, "9aaa5c80ac5c")
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the group has no mesh-sdk pull request") {
|
||||
t.Errorf("a foreign pin with no SDK member: %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPinOnlyTheSDKHeadCarriesThatDownloadCannotResolveFailsNamingIssue1148(t *testing.T) { // 1624/AC5, ADR 0453 clause 12
|
||||
b := groupBench(t, head2[:12])
|
||||
b.steps.handlers["build_go_fetch"] = func(c stepCall) (any, error) {
|
||||
if c.args["repository"] == repoA {
|
||||
return nil, fmt.Errorf("go mod download: git.novox.be/novox/mesh-sdk/go@v0.1.14-0.20261021100209-%s: invalid version: unknown revision", head2[:12])
|
||||
}
|
||||
return b.fetch, nil
|
||||
}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "issue 1148") || !strings.Contains(v.Failed.Words, head2[:12]) {
|
||||
t.Errorf("verdict = %+v, want a failure naming issue 1148 and the commit", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPinThatCannotBeCheckedFailsAndNeverPasses(t *testing.T) { // ADR 0453 clause 12
|
||||
b := groupBench(t, "cb54756c3865")
|
||||
b.ex.Forge.(*fakeForge).err = fmt.Errorf("the forge is down")
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the forge is down") {
|
||||
t.Errorf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// trunkOnlyBench is a group of mesh-delivery#7 whose fetch holds two Go modules: modules/m, which the head holds, and
|
||||
// modules/new, which only the trunk holds (added on the trunk after the branch was cut). The SDK pin of each go.mod is given.
|
||||
func trunkOnlyBench(t *testing.T, headPin, trunkPin string) *bench {
|
||||
b := newBench(t)
|
||||
b.fetch.Modules = []string{"modules/m", "modules/new"}
|
||||
forge := &fakeForge{goMods: map[string]string{
|
||||
repoA + " modules/m/go.mod " + head1: capturedGoMod(t, headPin),
|
||||
repoA + " modules/m/go.mod " + trunkA: capturedGoMod(t, headPin),
|
||||
repoA + " modules/new/go.mod " + trunkA: capturedGoMod(t, trunkPin),
|
||||
}, onTrunk: map[string]bool{"cb54756c3865": true}}
|
||||
b.ex.Forge = forge
|
||||
return b
|
||||
}
|
||||
|
||||
func TestAModuleOnlyTheTrunkHoldsIsReadAtTheTrunkAndPasses(t *testing.T) { // 1845/AC1
|
||||
b := trunkOnlyBench(t, "cb54756c3865", "cb54756c3865")
|
||||
if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict; v.State != delivery.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAStalePinOfAModuleOnlyTheTrunkHoldsStillFailsNamingTheFileAndTheCommit(t *testing.T) { // 1845/AC2
|
||||
b := trunkOnlyBench(t, "cb54756c3865", "9aaa5c80ac5c")
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, repoA+"#7") || !strings.Contains(v.Failed.Words, "modules/new/go.mod") || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAModuleTheHeadHoldsIsJudgedByTheHeadsPin(t *testing.T) { // 1845/AC3
|
||||
b := trunkOnlyBench(t, "9aaa5c80ac5c", "cb54756c3865") // the head's pin is stale, the trunk's go.mod of the same module is fine
|
||||
b.ex.Forge.(*fakeForge).goMods[repoA+" modules/m/go.mod "+trunkA] = capturedGoMod(t, "cb54756c3865")
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "modules/m/go.mod") || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAReadThatFailsForAnotherReasonStillFailsAndIsNotTriedAtTheTrunk(t *testing.T) { // 1845/AC4
|
||||
b := trunkOnlyBench(t, "cb54756c3865", "cb54756c3865")
|
||||
b.ex.Forge.(*fakeForge).errAt = map[string]error{head1: fmt.Errorf("the forge answered 500")}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "modules/m/go.mod") || !strings.Contains(v.Failed.Words, "the forge answered 500") {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
if got := packagesOfFetch(t, b, "novox/mesh-catalog"); len(got) != 2 || got[SDKModulePath] != trunkS || got[contract] != trunkA {
|
||||
t.Errorf("a pull request alone is built against %v, want every package at its latest published version", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +85,6 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR
|
||||
b := newBench(t)
|
||||
b.ex.Trunks = trunksOf(t, map[string]string{controller: trunkA, catalogue: trunkS, host: trunkA2, SDKRepository: "9999999999999999999999999999999999999999"})
|
||||
b.ex.Runs = runsAt{"mesh-host": hostRuns}
|
||||
forge := &fakeForge{goMods: map[string]string{controller + " go.mod " + head1: capturedGoMod(t, "cb54756c3865"),
|
||||
catalogue + " go.mod " + head2: "module git.novox.be/novox/mesh-catalog\n"}, onTrunk: map[string]bool{"cb54756c3865": true}}
|
||||
b.ex.Forge = forge
|
||||
// A group of the controller and the catalogue: the controller's tests read the catalogue's trunk, not the group's head.
|
||||
out := b.judge(t, caseOf(t, pullOf(controller, 7, head1), pullOf(catalogue, 12, head2)))
|
||||
// ADR 0548 §6, issue 568's own example: the verdict names the node-engine at the commit the mesh runs, and mesh-sdk at the
|
||||
@@ -253,8 +99,10 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR
|
||||
if h := got[host]; h.Commit != hostRuns {
|
||||
t.Errorf("the node-engine beside the controller = %+v, want the commit the mesh runs %s", h, hostRuns)
|
||||
}
|
||||
if s := got[SDKRepository]; s.Commit != "cb54756c3865"+strings.Repeat("0", 28) {
|
||||
t.Errorf("mesh-sdk beside the controller = %+v, want the commit its go.mod pins, whole", s)
|
||||
// ADR 0573 §2: the controller is built against mesh-sdk's latest published version, so its tests read that one too,
|
||||
// not the commit its go.mod names.
|
||||
if s := got[SDKRepository]; s.Commit != "9999999999999999999999999999999999999999" {
|
||||
t.Errorf("mesh-sdk beside the controller = %+v, want its trunk", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,36 +131,12 @@ func TestTheNodeEnginesTestsHaveTheControllerBesideThem(t *testing.T) {
|
||||
}
|
||||
b = newBench(t)
|
||||
b.ex.Trunks = trunksOf(t, map[string]string{host: trunkA, controllerRepo: trunkA2, catalogueRepository: trunkS, SDKRepository: trunkS})
|
||||
b.ex.Forge = &fakeForge{goMods: map[string]string{controllerRepo + " go.mod " + head2: capturedGoMod(t, "cb54756c3865")},
|
||||
onTrunk: map[string]bool{"cb54756c3865": true}}
|
||||
b.judge(t, hostCase(t, pullOf(host, 5, head1), pullOf(controllerRepo, 9, head2)))
|
||||
if c := besideOf(t, b, host)[controllerRepo]; c.Commit != head2 || c.Ref != "refs/pull/9/head" {
|
||||
t.Errorf("the controller beside the node-engine in a group = %+v, want the member's head %s", c, head2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThePinOfTheControllerThatCannotBeReadFailsTheCheck(t *testing.T) { // review D2
|
||||
b := newBench(t)
|
||||
b.ex.Trunks = trunksOf(t, map[string]string{"novox/mesh-controller": trunkA})
|
||||
b.ex.Forge = &fakeForge{goMods: map[string]string{}, onTrunk: map[string]bool{}}
|
||||
v := b.judge(t, caseOf(t, pullOf("novox/mesh-controller", 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "go.mod") {
|
||||
t.Errorf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAForgeThatIsNotHeardFailsAtTheForgeAndIsJudgedAgainWhenTheNodesChange(t *testing.T) { // 1624 C7
|
||||
b := newBench(t)
|
||||
b.ex.Forge = &fakeForge{err: &Unheard{Err: fmt.Errorf("nats: no responders available for request")}}
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
||||
if v.State != delivery.VerdictFail || v.Failed.Machine != "forge" {
|
||||
t.Fatalf("verdict = %+v, want failed at the forge", v)
|
||||
}
|
||||
if !IsNodesFailure(v) {
|
||||
t.Errorf("a forge that was not heard is not the change's failure: %+v is not judged again when the nodes change", v.Failed)
|
||||
}
|
||||
}
|
||||
|
||||
// 1887/A12: the change's own repository is placed beside it at the trunk it is merged onto, for every repository, so a
|
||||
// shrink-only list test reads the trunk's copy at $MESH_CHECK_BESIDE/<name>.
|
||||
func TestTheChangesOwnRepositoryIsBesideItAtItsTrunk(t *testing.T) {
|
||||
@@ -376,44 +200,3 @@ func TestTheNodeEnginesCheckFailsWhereTheControllersTrunkIsNotKnown(t *testing.T
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// sameBranchBench is a try of mesh-delivery's feat/r6 at head1 whose go.mod pins the SDK at pin, with the SDK's feat/r6 at head2.
|
||||
func sameBranchBench(t *testing.T, pin string) (*bench, *Case) {
|
||||
b := groupBench(t, pin)
|
||||
b.ex.Forge.(*fakeForge).branches = map[string]map[string]string{
|
||||
repoA: {"main": trunkA, "feat/r6": head1},
|
||||
repoSDK: {"main": "cb54756c3865" + strings.Repeat("0", 28), "feat/r6": head2},
|
||||
}
|
||||
c := caseOf(t, pullOf(repoA, 7, head1))
|
||||
c.Try = &TryOptions{}
|
||||
return b, c
|
||||
}
|
||||
|
||||
func TestATryPinnedToTheHeadOfTheSDKBranchOfTheTriedBranchPasses(t *testing.T) { // 1921/AC1
|
||||
b, c := sameBranchBench(t, head2[:12])
|
||||
if v := b.judge(t, c).Verdict; v.State != delivery.VerdictPass {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if n := c.Notes(); len(n) != 1 || n[0] != "pin accepted: head of mesh-sdk feat/r6" {
|
||||
t.Errorf("notes = %q", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestATryPinnedToAnotherCommitIsStillRefused(t *testing.T) { // 1921/AC2
|
||||
b, c := sameBranchBench(t, "9aaa5c80ac5c")
|
||||
v := b.judge(t, c).Verdict
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") || !strings.Contains(v.Failed.Words, "neither on mesh-sdk's trunk nor the group's head") {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
if len(c.Notes()) != 0 {
|
||||
t.Errorf("notes = %q", c.Notes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMergeCheckWithTheSameBranchPinIsStillRefusedWithoutAGroupPullRequest(t *testing.T) { // 1921/AC3
|
||||
b, _ := sameBranchBench(t, head2[:12])
|
||||
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict // no Try: a pull request's check
|
||||
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the group has no mesh-sdk pull request") {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) {
|
||||
if t.Tree == "" || !digest64.MatchString(t.Manifest) {
|
||||
continue
|
||||
}
|
||||
mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths}
|
||||
mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths, Packages: t.Packages}
|
||||
}
|
||||
}
|
||||
if merge == "" {
|
||||
|
||||
@@ -12,12 +12,13 @@ func TestAPreviewCarriesTheNamedPathsObjectsOfAFetchsModuleTrees(t *testing.T) {
|
||||
var f Fetched
|
||||
// As buildstep.ModuleTrees answers a module naming its paths: the named tree and the objects.
|
||||
raw := `{"workspace":"w","merge":"6666666666666666666666666666666666666666","trees":{".":{"tree":"named:aa","manifest":"` +
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + `","paths":{"src":"1111111111111111111111111111111111111111"}}}}`
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + `","paths":{"src":"1111111111111111111111111111111111111111"},"packages":["git.novox.be/novox/mesh-sdk/go"]}}}`
|
||||
if err := json.Unmarshal([]byte(raw), &f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pv, ok := PreviewOf(Pull{Repository: "novox/mesh-sdk", Head: "h", Trunk: "t"}, []Fetched{f})
|
||||
if !ok || !reflect.DeepEqual(pv.Modules["."].Paths, map[string]string{"src": "1111111111111111111111111111111111111111"}) || pv.Modules["."].Tree != "named:aa" {
|
||||
if !ok || !reflect.DeepEqual(pv.Modules["."].Paths, map[string]string{"src": "1111111111111111111111111111111111111111"}) || pv.Modules["."].Tree != "named:aa" ||
|
||||
!reflect.DeepEqual(pv.Modules["."].Packages, []string{"git.novox.be/novox/mesh-sdk/go"}) { // and the packages it uses (issue 1585)
|
||||
t.Errorf("preview = %+v, want the module's named tree and its objects", pv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ type MergeTree struct {
|
||||
// Paths are, of a module whose every artifact names its paths, each named path's object; Tree is then their named
|
||||
// tree (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0499 clause 2).
|
||||
Paths map[string]string `json:"paths,omitempty"`
|
||||
// Packages are the mesh packages the directory's go.sum names (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0573 §2).
|
||||
Packages []string `json:"packages,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON takes a string as the tree alone, or an object.
|
||||
|
||||
@@ -17,5 +17,3 @@ novox/mesh-delivery at main, and forge-compare-trunk.json the answer of gitea_co
|
||||
trunk: no commit main does not hold); both called through the mesh on 2026-10-21 for issue 1624. forge-compare-branch.json is the answer of the same call for a
|
||||
head that is not on the trunk (novox/mesh-sdk base main, head afbe371003a5, the branch feat/r4c5-sdk), shortened to two of its seven commits and one of its
|
||||
twenty-six files, with the head and the totals set to match.
|
||||
|
||||
forge-commits.json is the answer of the forge's gitea_commits for novox/mesh-sdk, ref cb54756c3865, limit 1, called through the mesh on 2026-10-21 (review D2).
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"commits": [
|
||||
{
|
||||
"author": "mesh-tools",
|
||||
"date": "2026-10-21T10:02:09Z",
|
||||
"sha": "cb54756c3865df5ac9c0dc0481ad9ebe2bf013a1",
|
||||
"subject": "Merge pull request 'Give delivery.Module the toolchains its fingerprint was computed with, and '.' as a root module's path, so the builder builds a key only with its own toolchain (issue 1615)' (#55) from fix/1615-module-toolchains-root into main"
|
||||
}
|
||||
],
|
||||
"more": true,
|
||||
"page": 1,
|
||||
"ref": "cb54756c3865",
|
||||
"repository": "novox/mesh-sdk"
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-forge-bridge
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030201806-eff81f1220e2
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030201806-eff81f1220e2 h1:RF73nVI1dTrrmlFosVDTMJmpUQ+rEeB7++BHH/RvZFg=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030201806-eff81f1220e2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0 h1:q6ywchpTM9fwaJ1UNwDSuOjb5FYDFcsaeiTrKoB4scc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -151,7 +151,7 @@ func ConvertKept(subject string, data []byte, trees Trees, files Files, kept Kep
|
||||
if action == delivery.PullUpdated && p.ModuleDirsSaid {
|
||||
f.Dirs, f.DirsKnown = p.ModuleDirs, true
|
||||
var err error
|
||||
if f.Manifests, f.Locks, err = inputsOf(p.Owner, p.Repo, p.HeadSHA, p.ModuleDirs, files); err != nil {
|
||||
if f.Manifests, f.Locks, f.Packages, _, err = inputsRead(p.Owner, p.Repo, p.HeadSHA, p.ModuleDirs, files); err != nil {
|
||||
return Message{}, fmt.Errorf("%w: %s: the manifests of %s/%s at the head %s: %v", ErrRetry, subject, p.Owner, p.Repo, p.HeadSHA, err)
|
||||
}
|
||||
}
|
||||
@@ -190,14 +190,15 @@ type read struct {
|
||||
// order the calls finish in, and the error is that of the first failing file in the order of dirs and LockFiles. After
|
||||
// a failure no further file is asked for, and inputsOf returns only when every call it started has returned.
|
||||
func inputsOf(owner, repo, ref string, dirs []string, files Files) (manifests map[string]string, locks map[string]map[string]string, err error) {
|
||||
manifests, locks, _, err = inputsRead(owner, repo, ref, dirs, files)
|
||||
manifests, locks, _, _, err = inputsRead(owner, repo, ref, dirs, files)
|
||||
return manifests, locks, err
|
||||
}
|
||||
|
||||
// inputsRead is inputsOf, and also answers, on a failure too, the directories whose manifest and lock files were all
|
||||
// read, with their manifests and lock hashes: a merge keeps them by their tree, so its next try reads only the rest
|
||||
// (issue 1126).
|
||||
func inputsRead(owner, repo, ref string, dirs []string, files Files) (manifests map[string]string, locks map[string]map[string]string, whole []string, err error) {
|
||||
// inputsRead is inputsOf, and also answers the mesh packages each directory's go.sum names (delivery.MeshPackagesOf,
|
||||
// novox/hq ADR 0573 §2; issue 1585) and, on a failure too, the directories whose manifest and lock files were all read,
|
||||
// with their manifests and lock hashes: a merge keeps them by their tree, so its next try reads only the rest (issue
|
||||
// 1126).
|
||||
func inputsRead(owner, repo, ref string, dirs []string, files Files) (manifests map[string]string, locks map[string]map[string]string, packages map[string][]string, whole []string, err error) {
|
||||
at := func(dir, name string) string {
|
||||
if dir == "" {
|
||||
return name
|
||||
@@ -241,6 +242,7 @@ func inputsRead(owner, repo, ref string, dirs []string, files Files) (manifests
|
||||
wg.Wait()
|
||||
manifests = map[string]string{}
|
||||
locks = map[string]map[string]string{}
|
||||
packages = map[string][]string{}
|
||||
broken := map[string]bool{} // a directory with a file not read (failed, or never asked after a failure)
|
||||
for _, r := range reads {
|
||||
switch {
|
||||
@@ -260,6 +262,11 @@ func inputsRead(owner, repo, ref string, dirs []string, files Files) (manifests
|
||||
locks[r.dir] = map[string]string{}
|
||||
}
|
||||
locks[r.dir][path.Base(r.path)] = hex.EncodeToString(sum[:])
|
||||
if path.Base(r.path) == "go.sum" {
|
||||
if p := delivery.MeshPackagesOf(r.raw); len(p) > 0 {
|
||||
packages[r.dir] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
@@ -271,8 +278,9 @@ func inputsRead(owner, repo, ref string, dirs []string, files Files) (manifests
|
||||
for dir := range broken {
|
||||
delete(manifests, dir)
|
||||
delete(locks, dir)
|
||||
delete(packages, dir)
|
||||
}
|
||||
return manifests, locks, whole, err
|
||||
return manifests, locks, packages, whole, err
|
||||
}
|
||||
if len(manifests) == 0 {
|
||||
manifests = nil
|
||||
@@ -280,7 +288,10 @@ func inputsRead(owner, repo, ref string, dirs []string, files Files) (manifests
|
||||
if len(locks) == 0 {
|
||||
locks = nil
|
||||
}
|
||||
return manifests, locks, whole, nil
|
||||
if len(packages) == 0 {
|
||||
packages = nil
|
||||
}
|
||||
return manifests, locks, packages, whole, nil
|
||||
}
|
||||
|
||||
// merged is the merged message of a merge into the trunk: the trees of the merge commit, asked once.
|
||||
@@ -301,15 +312,19 @@ func merged(subject string, p pull, trees Trees, files Files, kept Kept) (Messag
|
||||
names = append(names, dir)
|
||||
}
|
||||
slices.Sort(names)
|
||||
manifests, locks, toRead := keptInputs(names, dirs, kept)
|
||||
read, readLocks, whole, err := inputsRead(p.Owner, p.Repo, p.MergeSHA, toRead, files)
|
||||
manifests, locks, packages, toRead := keptInputs(names, dirs, kept)
|
||||
read, readLocks, readPackages, whole, err := inputsRead(p.Owner, p.Repo, p.MergeSHA, toRead, files)
|
||||
if err != nil {
|
||||
keep(whole, dirs, read, readLocks, kept) // what was read whole is not asked again at the next try
|
||||
keep(whole, dirs, read, readLocks, readPackages, kept) // what was read whole is not asked again at the next try
|
||||
return Message{}, fmt.Errorf("%w: %s: the manifests of %s at the merge commit %s (%d of %d module directories read from the forge, the others kept by their tree): %v", ErrRetry, subject, repository, p.MergeSHA, len(toRead), len(names), err)
|
||||
}
|
||||
keep(toRead, dirs, read, readLocks, kept)
|
||||
keep(toRead, dirs, read, readLocks, readPackages, kept)
|
||||
maps.Copy(manifests, read)
|
||||
maps.Copy(locks, readLocks)
|
||||
maps.Copy(packages, readPackages)
|
||||
if len(packages) == 0 {
|
||||
packages = nil
|
||||
}
|
||||
if len(manifests) == 0 {
|
||||
manifests = nil
|
||||
}
|
||||
@@ -321,7 +336,7 @@ func merged(subject string, p pull, trees Trees, files Files, kept Kept) (Messag
|
||||
return Message{}, unreadable(subject, "%v", err)
|
||||
}
|
||||
f := delivery.Forge{Kind: delivery.ForgeMerged, Repository: repository, Branch: p.Head, Number: p.Number,
|
||||
Head: p.HeadSHA, Merge: p.MergeSHA, Root: root, Trees: dirs, Manifests: manifests, Locks: locks}
|
||||
Head: p.HeadSHA, Merge: p.MergeSHA, Root: root, Trees: dirs, Manifests: manifests, Locks: locks, Packages: packages}
|
||||
if repository == CatalogRepository {
|
||||
// The catalogue's predefined list at the merge (novox/hq ADR 0565 §2): the indexer keeps it for mesh-config.
|
||||
raw, err := files(p.Owner, p.Repo, PredefinedFile, p.MergeSHA)
|
||||
|
||||
@@ -79,6 +79,10 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
||||
if want := map[string]map[string]string{"modules/a": {"go.sum": hexSum(sum)}}; !reflect.DeepEqual(f.Locks, want) {
|
||||
t.Errorf("locks = %v, want %v", f.Locks, want)
|
||||
}
|
||||
// Issue 1585 (ADR 0573 §2): and the mesh packages each go.sum names.
|
||||
if want := map[string][]string{"modules/a": {"git.novox.be/novox/mesh-sdk/go"}}; !reflect.DeepEqual(f.Packages, want) {
|
||||
t.Errorf("packages = %v, want %v", f.Packages, want)
|
||||
}
|
||||
// ADR 0567: placement is the seat mesh-placement's: a merge of the catalogue carries no placement file and asks the
|
||||
// forge for none.
|
||||
forge.calls = nil
|
||||
@@ -124,14 +128,15 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
||||
|
||||
func TestPullWithKnownDirsCarriesTheSameAtTheHead(t *testing.T) { // 1448/AC2
|
||||
manifest := real(t, "module.json")
|
||||
forge := &forgeFiles{have: map[string][]byte{"modules/a/module.json": manifest}}
|
||||
forge := &forgeFiles{have: map[string][]byte{"modules/a/module.json": manifest, "modules/a/go.sum": []byte("git.novox.be/novox/mesh-sdk/go v0.1.7 h1:x=\n")}}
|
||||
msg, err := Convert(updatedSubject, updated(nil), nil, forge.get)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, _ := delivery.DecodeForge(msg.Data)
|
||||
if !reflect.DeepEqual(f.Manifests, map[string]string{"modules/a": string(manifest)}) || len(f.Locks) != 0 {
|
||||
t.Errorf("manifests %v locks %v", f.Manifests, f.Locks)
|
||||
if !reflect.DeepEqual(f.Manifests, map[string]string{"modules/a": string(manifest)}) || len(f.Locks) != 1 ||
|
||||
!reflect.DeepEqual(f.Packages, map[string][]string{"modules/a": {"git.novox.be/novox/mesh-sdk/go"}}) {
|
||||
t.Errorf("manifests %v locks %v packages %v", f.Manifests, f.Locks, f.Packages)
|
||||
}
|
||||
if len(forge.calls) == 0 || forge.calls[0].ref != head {
|
||||
t.Errorf("calls %v, want the head %s", forge.calls, head)
|
||||
@@ -389,4 +394,14 @@ func mergeReadsOnlyChangedDirs(t *testing.T) {
|
||||
if len(failing.calls) != 3 {
|
||||
t.Errorf("the try after a failure read %d files, want only modules/b's 3", len(failing.calls))
|
||||
}
|
||||
// Issue 1585: inputs kept before the bridge read the packages are read again, so no module loses the packages it uses.
|
||||
for tree, in := range kept2 {
|
||||
in.Packages, in.PackagesRead = nil, false
|
||||
kept2[tree] = in
|
||||
}
|
||||
failing.calls = nil
|
||||
again, err = ConvertKept(mergedSubject, mergedBody(nil), treesOf(&calls, "r1", trees, nil), failing.get, kept2)
|
||||
if err != nil || string(again.Data) != string(whole.Data) || len(failing.calls) != 6 {
|
||||
t.Errorf("inputs kept without their packages: %d files read, want 6; same message: %v (%v)", len(failing.calls), string(again.Data) == string(whole.Data), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,14 @@ package bridge
|
||||
// every byte under it, so the manifest and the lock-file hashes read once at a tree hash are those of every later commit
|
||||
// whose directory has the same tree: a merge reads the forge only for the directories it changed.
|
||||
|
||||
// Inputs is what the bridge read of one module directory at one tree hash: its manifest and its lock files' hex sha256
|
||||
// by name (none when it has none).
|
||||
// Inputs is what the bridge read of one module directory at one tree hash: its manifest, its lock files' hex sha256
|
||||
// by name (none when it has none) and the mesh packages its go.sum names (novox/hq ADR 0573 §2). PackagesRead says the
|
||||
// packages were read: a value kept before the bridge read them is not taken, since its packages are not known.
|
||||
type Inputs struct {
|
||||
Manifest string `json:"manifest"`
|
||||
Locks map[string]string `json:"locks,omitempty"`
|
||||
Manifest string `json:"manifest"`
|
||||
Locks map[string]string `json:"locks,omitempty"`
|
||||
Packages []string `json:"packages,omitempty"`
|
||||
PackagesRead bool `json:"packages-read,omitempty"`
|
||||
}
|
||||
|
||||
// Kept holds Inputs by a directory's git tree hash (the bridge's state bucket inputs). Get answers false for a tree it
|
||||
@@ -22,8 +25,8 @@ type Kept interface {
|
||||
}
|
||||
|
||||
// keptInputs splits dirs into the inputs kept for their tree hash and the directories still to read from the forge.
|
||||
func keptInputs(dirs []string, trees map[string]string, kept Kept) (manifests map[string]string, locks map[string]map[string]string, read []string) {
|
||||
manifests, locks = map[string]string{}, map[string]map[string]string{}
|
||||
func keptInputs(dirs []string, trees map[string]string, kept Kept) (manifests map[string]string, locks map[string]map[string]string, packages map[string][]string, read []string) {
|
||||
manifests, locks, packages = map[string]string{}, map[string]map[string]string{}, map[string][]string{}
|
||||
for _, dir := range dirs {
|
||||
tree := trees[dir]
|
||||
if kept == nil || tree == "" {
|
||||
@@ -31,7 +34,7 @@ func keptInputs(dirs []string, trees map[string]string, kept Kept) (manifests ma
|
||||
continue
|
||||
}
|
||||
in, ok := kept.Get(tree)
|
||||
if !ok {
|
||||
if !ok || !in.PackagesRead {
|
||||
read = append(read, dir)
|
||||
continue
|
||||
}
|
||||
@@ -39,19 +42,22 @@ func keptInputs(dirs []string, trees map[string]string, kept Kept) (manifests ma
|
||||
if len(in.Locks) > 0 {
|
||||
locks[dir] = in.Locks
|
||||
}
|
||||
if len(in.Packages) > 0 {
|
||||
packages[dir] = in.Packages
|
||||
}
|
||||
}
|
||||
return manifests, locks, read
|
||||
return manifests, locks, packages, read
|
||||
}
|
||||
|
||||
// keep puts what was read of each directory under its tree hash.
|
||||
func keep(dirs []string, trees map[string]string, manifests map[string]string, locks map[string]map[string]string, kept Kept) {
|
||||
func keep(dirs []string, trees map[string]string, manifests map[string]string, locks map[string]map[string]string, packages map[string][]string, kept Kept) {
|
||||
if kept == nil {
|
||||
return
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
raw, ok := manifests[dir]
|
||||
if tree := trees[dir]; ok && tree != "" {
|
||||
kept.Put(tree, Inputs{Manifest: raw, Locks: locks[dir]})
|
||||
kept.Put(tree, Inputs{Manifest: raw, Locks: locks[dir], Packages: packages[dir], PackagesRead: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,14 @@ func readVia(call toolCaller) func(ctx context.Context, repository, sha, dir str
|
||||
out.Locks[dir] = map[string]string{}
|
||||
}
|
||||
out.Locks[dir][name] = hex.EncodeToString(sum[:])
|
||||
if name == "go.sum" {
|
||||
if p := delivery.MeshPackagesOf([]byte(c)); len(p) > 0 {
|
||||
if out.Packages == nil {
|
||||
out.Packages = map[string][]string{}
|
||||
}
|
||||
out.Packages[dir] = p // the mesh packages it uses, as the bridge reads them (ADR 0573 §2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-indexer
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0 h1:q6ywchpTM9fwaJ1UNwDSuOjb5FYDFcsaeiTrKoB4scc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -58,7 +58,7 @@ type baseFP func(module string) (fp, none string)
|
||||
// here, so a fingerprint is never given for a build that could not run. An archive, an image and an upstream resolve
|
||||
// nothing and have no toolchain. A base is a build.on entry that names a module.
|
||||
func inputsOf(module, raw, tree string, locks map[string]string, base baseFP) moduleInputs {
|
||||
return inputsOfNamed(module, raw, tree, locks, nil, base)
|
||||
return inputsOfNamed(module, raw, tree, locks, nil, nil, base)
|
||||
}
|
||||
|
||||
// inputsOfNamed is inputsOf with the git object of each path the message names for the module's directory (delivery
|
||||
@@ -66,7 +66,10 @@ func inputsOf(module, raw, tree string, locks map[string]string, base baseFP) mo
|
||||
// manifest, by mesh-sdk's one function (delivery.NamedTree), and keeps their objects, so a computation from kept inputs
|
||||
// gives the merge's value (novox/hq ADR 0499 clause 2); a message that does not carry the object of each such path gives it
|
||||
// none, naming the paths (clause 3). Every other module keeps its directory's tree, whatever objects are given.
|
||||
func inputsOfNamed(module, raw, tree string, locks, objects map[string]string, base baseFP) moduleInputs {
|
||||
//
|
||||
// packages are the mesh packages of other repositories the module's go.sum names (novox/hq ADR 0573 §2): a module with a
|
||||
// Go artifact keeps them, and the fingerprint takes the latest published version of each (Recompute).
|
||||
func inputsOfNamed(module, raw, tree string, locks, objects map[string]string, packages []string, base baseFP) moduleInputs {
|
||||
m, err := catalogue.ParseManifestForInputs([]byte(raw))
|
||||
if err != nil {
|
||||
return moduleInputs{None2: None2Unreadable + err.Error()}
|
||||
@@ -133,6 +136,10 @@ func inputsOfNamed(module, raw, tree string, locks, objects map[string]string, b
|
||||
}
|
||||
}
|
||||
}
|
||||
if usesGo(langs) && len(packages) > 0 {
|
||||
kept.Packages = slices.Clone(packages)
|
||||
sort.Strings(kept.Packages)
|
||||
}
|
||||
sort.Strings(kept.Bases)
|
||||
sort.Strings(missing)
|
||||
out := moduleInputs{Kept: &kept, ArtifactPaths: artifactPaths}
|
||||
@@ -152,6 +159,17 @@ func inputsOfNamed(module, raw, tree string, locks, objects map[string]string, b
|
||||
return out
|
||||
}
|
||||
|
||||
// usesGo is whether an artifact is built with Go: only a Go build resolves a mesh package by the version the build asks
|
||||
// (novox/hq ADR 0573 §2); a Node package moves its dependents as a base (ADR 0498 §5).
|
||||
func usesGo(langs map[string]string) bool {
|
||||
for _, l := range langs {
|
||||
if l == "go" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// languagesOf is the language of each artifact of the manifest that has a toolchain (a bundle or a package of go or
|
||||
// typescript), by artifact name: the artifacts a build module's toolchain enters the fingerprint of.
|
||||
func languagesOf(m catalogue.Manifest) map[string]string {
|
||||
|
||||
@@ -85,12 +85,13 @@ func (ix *Indexer) applyManifests(ctx context.Context, f delivery.Forge, drop bo
|
||||
if next.Manifest == "" {
|
||||
next.None2 = "the manifest cannot be read: it cannot be kept as a value"
|
||||
} else {
|
||||
in := inputsOfNamed(name, raw, f.Trees[dir], f.Locks[dir], objects[dir], ix.baseFP(ctx))
|
||||
packages := delivery.OtherPackages(f.Packages[dir], f.Repository) // its own repository's come with its tree
|
||||
in := inputsOfNamed(name, raw, f.Trees[dir], f.Locks[dir], objects[dir], packages, ix.baseFP(ctx))
|
||||
next.Inputs2, next.None2, next.ArtifactPaths = in.Kept, in.None2, in.ArtifactPaths
|
||||
if in.Kept == nil && strings.HasPrefix(in.None2, None2Unreadable) {
|
||||
// The manifest cannot be read by this SDK; the inputs the merge carried are kept all the same, so a later SDK
|
||||
// that reads it judges the module again at start with no merge (issue 1923).
|
||||
next.Inputs2 = unreadInputs(f.Trees[dir], f.Locks[dir], f.Paths[dir])
|
||||
next.Inputs2 = unreadInputs(f.Trees[dir], f.Locks[dir], f.Paths[dir], packages)
|
||||
}
|
||||
if in.Kept == nil && namedErr[dir] != "" {
|
||||
next.None2 = in.None2 + ": the forge could not say them: " + namedErr[dir]
|
||||
@@ -283,7 +284,7 @@ const None2Unreadable = "the manifest cannot be read: "
|
||||
// hashes by file name (the artifacts that own them are not known without the manifest) and the objects of the named
|
||||
// paths the message carried; nil when the message carried no tree. Judging the manifest again (rejudged) turns them into
|
||||
// the inputs of the module (issue 1923).
|
||||
func unreadInputs(tree string, locks, paths map[string]string) *delivery.KeptInputs2 {
|
||||
func unreadInputs(tree string, locks, paths map[string]string, packages []string) *delivery.KeptInputs2 {
|
||||
if tree == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -294,5 +295,6 @@ func unreadInputs(tree string, locks, paths map[string]string) *delivery.KeptInp
|
||||
if len(paths) > 0 {
|
||||
in.Paths = maps.Clone(paths)
|
||||
}
|
||||
in.Packages = slices.Clone(packages)
|
||||
return in
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestAMessageWithoutTheNamedPathsObjectsGivesTheModuleNoneNamingThem(t *test
|
||||
}
|
||||
|
||||
func TestAModuleNamingNoPathsKeepsItsDirectorysTree(t *testing.T) { // I7, ADR 0499 clause 2: every other module is unchanged
|
||||
in := inputsOfNamed("avahi", catalogueManifest(t, "avahi"), "avahitree", map[string]string{"go.sum": avahiGoSum}, map[string]string{"x": "1111111111111111111111111111111111111111"}, noBases)
|
||||
in := inputsOfNamed("avahi", catalogueManifest(t, "avahi"), "avahitree", map[string]string{"go.sum": avahiGoSum}, map[string]string{"x": "1111111111111111111111111111111111111111"}, nil, noBases)
|
||||
if in.Kept == nil || in.Kept.Tree != "avahitree" || in.Kept.Paths != nil {
|
||||
t.Errorf("inputs = %+v, want the directory's tree and no paths", in.Kept)
|
||||
}
|
||||
@@ -79,10 +79,10 @@ func TestACandidatesModuleTakesTheNamedPathsObjectsFromThePreview(t *testing.T)
|
||||
named, _ := delivery.NamedTree(digest, sdkObjects)
|
||||
with := delivery.Preview{Modules: map[string]delivery.PreviewModule{".": {Tree: named, Manifest: digest, Paths: sdkObjects}}}
|
||||
without := delivery.Preview{Modules: map[string]delivery.PreviewModule{".": {Tree: "roottree", Manifest: digest}}}
|
||||
if got := ix.candidateModule(context.Background(), "mesh-sdk", ".", without); !strings.Contains(got.None2, "named paths") {
|
||||
if got := ix.candidateModule(context.Background(), "mesh-sdk", ".", without, nil, nil); !strings.Contains(got.None2, "named paths") {
|
||||
t.Errorf("without the objects: %+v, want none naming the named paths", got)
|
||||
}
|
||||
got := ix.candidateModule(context.Background(), "mesh-sdk", ".", with)
|
||||
got := ix.candidateModule(context.Background(), "mesh-sdk", ".", with, nil, nil)
|
||||
if strings.Contains(got.None2, "named paths") || got.Manifest != digest {
|
||||
t.Errorf("with the objects: %+v, want them taken (none only for what else is missing)", got)
|
||||
}
|
||||
|
||||
@@ -147,6 +147,14 @@ func (ix *Indexer) withPreviews(ctx context.Context, c *delivery.Candidate) bool
|
||||
rev, heads := ix.source.rev, ix.source.heads
|
||||
ix.mu.Unlock()
|
||||
changed, read := false, false
|
||||
// A package one of the candidate's pull requests publishes is built at that pull request's head, every other at its
|
||||
// latest published version (novox/hq ADR 0573 §4): a group's members build against the group's own package.
|
||||
members := map[string]string{}
|
||||
for _, p := range c.Pulls {
|
||||
members[delivery.RepositoryName(p.Repository)] = p.Head.Head
|
||||
}
|
||||
var held map[string]delivery.Module
|
||||
listed := false
|
||||
for i := range c.Pulls {
|
||||
p := &c.Pulls[i]
|
||||
pv, found, stale := ix.previewOf(heads, p.Repository, p.Head.Head)
|
||||
@@ -154,17 +162,20 @@ func (ix *Indexer) withPreviews(ctx context.Context, c *delivery.Candidate) bool
|
||||
continue
|
||||
}
|
||||
read = true
|
||||
if !listed {
|
||||
var err error
|
||||
if held, err = ix.heldModules(ctx); err != nil {
|
||||
ix.logf("the modules cannot be listed, so no touched module's directory or package version is known: %v", err)
|
||||
}
|
||||
listed = true
|
||||
}
|
||||
// A touched module's directory: the one the modules bucket holds of the repository, else the preview's directory
|
||||
// whose manifest is the one the pull request gave the module (a module the pull request adds; ADR 0501 clause 6).
|
||||
dirs := map[string]string{}
|
||||
if held, err := ix.heldModules(ctx); err == nil {
|
||||
for name, m := range held {
|
||||
if delivery.SameRepository(m.Repository, p.Repository) && m.Path != "" {
|
||||
dirs[name] = m.Path
|
||||
}
|
||||
for name, m := range held {
|
||||
if delivery.SameRepository(m.Repository, p.Repository) && m.Path != "" {
|
||||
dirs[name] = m.Path
|
||||
}
|
||||
} else {
|
||||
ix.logf("the modules cannot be listed, so no touched module's directory is known: %v", err)
|
||||
}
|
||||
for _, m := range p.Modules {
|
||||
if _, known := dirs[m]; known || p.Fps[m].Manifest == "" {
|
||||
@@ -190,7 +201,7 @@ func (ix *Indexer) withPreviews(ctx context.Context, c *delivery.Candidate) bool
|
||||
next.None2 = NoneNoDirectory
|
||||
break
|
||||
}
|
||||
next = ix.candidateModule(ctx, m, dir, pv)
|
||||
next = ix.candidateModule(ctx, m, dir, pv, held, members)
|
||||
}
|
||||
if fps == nil {
|
||||
fps = map[string]delivery.CandidateModule{}
|
||||
@@ -214,9 +225,10 @@ func (ix *Indexer) withPreviews(ctx context.Context, c *delivery.Candidate) bool
|
||||
}
|
||||
|
||||
// candidateModule is the fingerprint of module m, whose directory is dir, at the preview: the module's inputs from the
|
||||
// merge (tree, manifest, locks), its bases' and the builder's fingerprints as the trunk's modules hold them, and the toolchains
|
||||
// the build modules agree on, by the rules Recompute applies to a module of the trunk.
|
||||
func (ix *Indexer) candidateModule(ctx context.Context, m, dir string, pv delivery.Preview) delivery.CandidateModule {
|
||||
// merge (tree, manifest, locks), its bases' and the builder's fingerprints as the trunk's modules hold them, the toolchains
|
||||
// the build modules agree on, and the version of each mesh package it uses (candidatePackages), by the rules Recompute
|
||||
// applies to a module of the trunk.
|
||||
func (ix *Indexer) candidateModule(ctx context.Context, m, dir string, pv delivery.Preview, held map[string]delivery.Module, members map[string]string) delivery.CandidateModule {
|
||||
pm, ok := pv.Modules[dir]
|
||||
if !ok && dir == "" {
|
||||
pm, ok = pv.Modules[delivery.RootPath]
|
||||
@@ -242,7 +254,7 @@ func (ix *Indexer) candidateModule(ctx context.Context, m, dir string, pv delive
|
||||
out.None2 = NonePreviewNoManifest
|
||||
return out
|
||||
}
|
||||
in := inputsOfNamed(m, kept.JSON, pm.Tree, pm.Locks, pm.Paths, ix.baseFP(ctx))
|
||||
in := inputsOfNamed(m, kept.JSON, pm.Tree, pm.Locks, pm.Paths, delivery.OtherPackages(pm.Packages, pv.Repository), ix.baseFP(ctx))
|
||||
if in.Kept == nil {
|
||||
out.None2 = in.None2
|
||||
return out
|
||||
@@ -262,14 +274,32 @@ func (ix *Indexer) candidateModule(ctx context.Context, m, dir string, pv delive
|
||||
toolchains[artifact] = t.tc
|
||||
}
|
||||
inputs := *in.Kept // no builder input (ADR 0530 §1)
|
||||
fp, none := delivery.SourceFingerprint2(inputs.With(m, toolchains))
|
||||
versions := candidatePackages(inputs.Packages, held, members)
|
||||
fp, none := delivery.SourceFingerprint2(inputs.WithPackages(m, toolchains, versions))
|
||||
if fp == "" {
|
||||
out.None2 = none
|
||||
return out
|
||||
}
|
||||
out.FP2 = fp
|
||||
out.FP2, out.Packages = fp, versions
|
||||
if len(inputs.Paths) > 0 {
|
||||
out.Paths = inputs.Paths // the builder's fetch for the candidate names them (ADR 0499 clause 4)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// candidatePackages is the version each package of a candidate's module is built against (novox/hq ADR 0573 §2, §4): the
|
||||
// head of the candidate's pull request whose repository publishes it (members, by repository), else its latest published
|
||||
// version, as the trunk's modules hold it. A package with neither is left out, and the fingerprint is then none.
|
||||
func candidatePackages(packages []string, held map[string]delivery.Module, members map[string]string) delivery.PackageVersions {
|
||||
versions, _ := delivery.PackageVersionsOf(packages, held)
|
||||
for _, p := range packages {
|
||||
repository, _, _ := delivery.PackageRepository(p)
|
||||
if head := members[repository]; head != "" {
|
||||
if versions == nil {
|
||||
versions = delivery.PackageVersions{}
|
||||
}
|
||||
versions[p] = head
|
||||
}
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
@@ -109,6 +109,24 @@ func TestACandidatesModuleGetsTheFingerprintATrunkModuleOfTheSameTreeHas(t *test
|
||||
if other := fpOf(t, cands); other.FP2 == "" || other.FP2 == trunk.FP2 {
|
||||
t.Errorf("a changed tree: %+v", other)
|
||||
}
|
||||
// Issue 1585 (ADR 0573 §2, §4): a candidate's module is built against the head of the candidate's pull request that
|
||||
// publishes a package it uses, else against the package's latest published version; a preview naming a package of
|
||||
// mesh-sdk that nothing published gives no fingerprint, and one of its own repository's comes with its tree.
|
||||
const sdk, own = "git.novox.be/novox/mesh-sdk/go", "git.novox.be/novox/mesh-catalog/contract"
|
||||
held := map[string]delivery.Module{"mesh-sdk": {Repository: "novox/mesh-sdk", Path: delivery.RootPath, Commit: trunkOld}}
|
||||
if got := candidatePackages([]string{sdk}, held, map[string]string{catalogueRepo: h1}); got[sdk] != trunkOld {
|
||||
t.Errorf("outside a group: %v, want mesh-sdk at its trunk %s", got, trunkOld)
|
||||
}
|
||||
if got := candidatePackages([]string{sdk}, held, map[string]string{catalogueRepo: h1, "novox/mesh-sdk": mergeSha}); got[sdk] != mergeSha {
|
||||
t.Errorf("in a group with mesh-sdk: %v, want its member's head %s", got, mergeSha)
|
||||
}
|
||||
m.Packages = []string{own, sdk}
|
||||
pv.Modules["modules/avahi"] = m
|
||||
pv.Merge = "8888888888888888888888888888888888888888"
|
||||
applyPreview(t, ix, pv)
|
||||
if got := fpOf(t, cands); got.FP2 != "" || got.None2 != delivery.NoPackageVersion+sdk {
|
||||
t.Errorf("a package nothing published: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnUnknownToolchainLeavesTheCandidatesModuleNamingIt(t *testing.T) { // 1445/AC2
|
||||
|
||||
@@ -24,6 +24,8 @@ type RepoRead struct {
|
||||
Trees map[string]string
|
||||
Manifests map[string]string
|
||||
Locks map[string]map[string]string
|
||||
// Packages are the mesh packages each directory's go.sum names (delivery.MeshPackagesOf, novox/hq ADR 0573 §2).
|
||||
Packages map[string][]string
|
||||
}
|
||||
|
||||
// IndexRegistered writes the module of a registration as a merge of its repository would, when the modules bucket holds
|
||||
@@ -129,6 +131,9 @@ func (ix *Indexer) indexAt(ctx context.Context, r Registration, force, reread bo
|
||||
if l := read.Locks[dir]; len(l) > 0 {
|
||||
f.Locks = map[string]map[string]string{dir: l}
|
||||
}
|
||||
if p := read.Packages[dir]; len(p) > 0 {
|
||||
f.Packages = map[string][]string{dir: p}
|
||||
}
|
||||
if _, err := ix.applyManifests(ctx, f, false); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli
|
||||
type result struct {
|
||||
fp, none string
|
||||
tcs map[string]delivery.ToolchainUsed // by language, when there is a fingerprint
|
||||
pkgs delivery.PackageVersions // the package versions, when there is a fingerprint
|
||||
}
|
||||
done := map[string]result{}
|
||||
visiting := map[string]bool{}
|
||||
@@ -251,6 +252,9 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli
|
||||
}
|
||||
toolchains[artifact] = t.tc
|
||||
}
|
||||
// The latest published version of each mesh package the module uses: the trunk merge of the module that holds it,
|
||||
// as this recompute holds it, so a package's merge moves every dependent in the same recompute (ADR 0573 §2).
|
||||
versions, _ := delivery.PackageVersionsOf(kept.Packages, held)
|
||||
var r result
|
||||
switch {
|
||||
case k.contexts:
|
||||
@@ -259,13 +263,13 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli
|
||||
r.none = strings.Join(missing, "; ")
|
||||
case noToolchain != "":
|
||||
r.none = noToolchain
|
||||
if fp, ok := stillComputed(module, kept, k, toolchains); ok {
|
||||
r = result{fp: fp, tcs: k.held.Toolchains}
|
||||
if fp, ok := stillComputed(module, kept, k, toolchains, versions); ok {
|
||||
r = result{fp: fp, tcs: k.held.Toolchains, pkgs: versions}
|
||||
}
|
||||
default:
|
||||
r.fp, r.none = delivery.SourceFingerprint2(kept.With(module, toolchains))
|
||||
r.fp, r.none = delivery.SourceFingerprint2(kept.WithPackages(module, toolchains, versions))
|
||||
if r.fp != "" {
|
||||
r.tcs = toolchainsUsed(toolchains)
|
||||
r.tcs, r.pkgs = toolchainsUsed(toolchains), versions
|
||||
}
|
||||
}
|
||||
done[module] = r
|
||||
@@ -278,7 +282,7 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli
|
||||
r := of(key, nil)
|
||||
if _, merged := overlay[key]; merged {
|
||||
final := overlay[key]
|
||||
final.FP2, final.None2, final.Inputs2, final.Toolchains = r.fp, r.none, k.cur.Inputs2, r.tcs
|
||||
final.FP2, final.None2, final.Inputs2, final.Toolchains, final.Packages = r.fp, r.none, k.cur.Inputs2, r.tcs, r.pkgs
|
||||
// What the bases gave is kept as it was while the value it gave does not change, so a merge
|
||||
// that moves nothing for the module writes nothing (as Recompute does).
|
||||
if value, _, found, err := ix.Modules.Get(ctx, key); err == nil && found {
|
||||
@@ -291,7 +295,7 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli
|
||||
finals[key] = final
|
||||
continue
|
||||
}
|
||||
if !stale[key] && k.cur.FP2 == r.fp && k.cur.None2 == r.none && reflect.DeepEqual(k.cur.Toolchains, r.tcs) {
|
||||
if !stale[key] && k.cur.FP2 == r.fp && k.cur.None2 == r.none && reflect.DeepEqual(k.cur.Toolchains, r.tcs) && reflect.DeepEqual(k.cur.Packages, r.pkgs) {
|
||||
continue
|
||||
}
|
||||
digest, inputs, paths := k.cur.Manifest, k.cur.Inputs2, k.cur.ArtifactPaths
|
||||
@@ -304,10 +308,10 @@ func (ix *Indexer) recomputeModules(ctx context.Context, overlay map[string]deli
|
||||
if err != nil || cur.Manifest != digest || cur.Inputs2 == nil {
|
||||
return nil, false, nil // a merge changed it meanwhile; its own recompute follows
|
||||
}
|
||||
if cur.FP2 == r.fp && cur.None2 == r.none && reflect.DeepEqual(cur.Toolchains, r.tcs) {
|
||||
if cur.FP2 == r.fp && cur.None2 == r.none && reflect.DeepEqual(cur.Toolchains, r.tcs) && reflect.DeepEqual(cur.Packages, r.pkgs) {
|
||||
return nil, false, nil
|
||||
}
|
||||
cur.FP2, cur.None2, cur.Toolchains = r.fp, r.none, r.tcs
|
||||
cur.FP2, cur.None2, cur.Toolchains, cur.Packages = r.fp, r.none, r.tcs, r.pkgs
|
||||
cur.Inputs2, cur.ArtifactPaths = inputs, paths
|
||||
data, err := delivery.EncodeModule(cur)
|
||||
wrote = err == nil
|
||||
@@ -397,7 +401,7 @@ func (ix *Indexer) rejudged(ctx context.Context, m catalogue.Manifest, cur deliv
|
||||
if cur.FP2 != "" || cur.Inputs2 == nil || !strings.HasPrefix(cur.None2, None2Unreadable) {
|
||||
return cur, false
|
||||
}
|
||||
in := inputsOfNamed(m.Module, raw, cur.Inputs2.Tree, cur.Inputs2.Locks, cur.Inputs2.Paths, ix.baseFP(ctx))
|
||||
in := inputsOfNamed(m.Module, raw, cur.Inputs2.Tree, cur.Inputs2.Locks, cur.Inputs2.Paths, cur.Inputs2.Packages, ix.baseFP(ctx))
|
||||
if in.Kept == nil {
|
||||
return cur, false
|
||||
}
|
||||
@@ -424,7 +428,7 @@ func hasContext(m catalogue.Manifest) bool {
|
||||
// computed and built). Only computing a new fingerprint needs the agreement: the held one is kept when its kept inputs,
|
||||
// with the toolchains it was computed with in place of the languages agreed on none, give it again. Any other input that
|
||||
// moved (a merge, a base, an agreed toolchain) gives another fingerprint, and the module has none until they agree.
|
||||
func stillComputed(module string, kept delivery.KeptInputs2, k *known, agreed map[string]delivery.Toolchain2) (string, bool) {
|
||||
func stillComputed(module string, kept delivery.KeptInputs2, k *known, agreed map[string]delivery.Toolchain2, versions delivery.PackageVersions) (string, bool) {
|
||||
if k.held.FP2 == "" {
|
||||
return "", false
|
||||
}
|
||||
@@ -440,7 +444,7 @@ func stillComputed(module string, kept delivery.KeptInputs2, k *known, agreed ma
|
||||
}
|
||||
toolchains[artifact] = delivery.Toolchain2{Language: language, Version: used.Version, Recipe: used.Recipe}
|
||||
}
|
||||
fp, none := delivery.SourceFingerprint2(kept.With(module, toolchains))
|
||||
fp, none := delivery.SourceFingerprint2(kept.WithPackages(module, toolchains, versions))
|
||||
return fp, none == "" && fp == k.held.FP2
|
||||
}
|
||||
|
||||
|
||||
@@ -294,6 +294,41 @@ func TestAModuleKeepsTheToolchainsItsFingerprintWasComputedWith(t *testing.T) {
|
||||
if n.FP2 == m.FP2 || n.Toolchains["go"].Version != "go1.27.2" {
|
||||
t.Errorf("after the upgrade: fp2 %q (was %q) toolchains %+v, want the new version with the new fingerprint", n.FP2, m.FP2, n.Toolchains)
|
||||
}
|
||||
// Issue 1585 (ADR 0573 §2): a module whose go.sum names mesh-sdk takes mesh-sdk's latest published version into its
|
||||
// fingerprint and keeps it beside its toolchains; a mesh-sdk trunk merge moves it with no merge of its own, and a module
|
||||
// using no package is not moved. With no published version held, it has none, naming the package.
|
||||
const sdk = "git.novox.be/novox/mesh-sdk/go"
|
||||
_, _, uses := newFormIndexerUnmerged(t, builderModules(t)...)
|
||||
uses.Packages = map[string][]string{"modules/avahi": {sdk}}
|
||||
if err := ix.Apply(context.Background(), uses); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u, _ := moduleOf(t, mods, "avahi"); u.FP2 != "" || u.None2 != delivery.NoPackageVersion+sdk {
|
||||
t.Errorf("no published mesh-sdk: fp2 %q none2 %q", u.FP2, u.None2)
|
||||
}
|
||||
tools, _ := moduleOf(t, mods, "mesh-tools")
|
||||
sdkMerge := func(commit, tree string) delivery.Module {
|
||||
t.Helper()
|
||||
f := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: "novox/mesh-sdk", Branch: "feat/x", Number: 3, Head: h1,
|
||||
Merge: commit, Root: tree, Trees: map[string]string{"": tree}, Manifests: map[string]string{"": `{"module":"mesh-sdk","version":"1"}`}}
|
||||
if err := ix.Apply(context.Background(), f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a, _ := moduleOf(t, mods, "avahi")
|
||||
if a.FP2 == "" || !reflect.DeepEqual(a.Packages, delivery.PackageVersions{sdk: commit}) {
|
||||
t.Fatalf("after mesh-sdk's merge %.8s: fp2 %q none2 %q packages %v", commit, a.FP2, a.None2, a.Packages)
|
||||
}
|
||||
if o, _ := moduleOf(t, mods, "mesh-tools"); o.FP2 != tools.FP2 || o.Packages != nil {
|
||||
t.Errorf("mesh-tools uses no package and moved: fp2 %q (was %q) packages %v", o.FP2, tools.FP2, o.Packages)
|
||||
}
|
||||
return a
|
||||
}
|
||||
first := sdkMerge(strings.Repeat("1", 40), "tree-sdk-1")
|
||||
second := sdkMerge(strings.Repeat("2", 40), "tree-sdk-2")
|
||||
if second.FP2 == first.FP2 {
|
||||
t.Errorf("a mesh-sdk trunk merge kept avahi's fingerprint %q", first.FP2)
|
||||
}
|
||||
n = second
|
||||
// Nodes that disagree keep the fingerprint already computed, with its toolchains (issue 1940, 2026-10-25 20:56: a
|
||||
// stale report blanked every module and the controller's take-in refused modules already built); only a new
|
||||
// fingerprint needs the agreement: a merge that moves the module's tree gives it none, naming the disagreement.
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-watchman
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030201806-eff81f1220e2
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030201806-eff81f1220e2 h1:RF73nVI1dTrrmlFosVDTMJmpUQ+rEeB7++BHH/RvZFg=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030201806-eff81f1220e2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0 h1:q6ywchpTM9fwaJ1UNwDSuOjb5FYDFcsaeiTrKoB4scc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101005726-af4e4e60a3a0/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op h1:1BOWQJweNyvZMlpAHXGLiZQn9S+QXGcz3xh94lC0w6E=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -36,14 +36,14 @@ const (
|
||||
// operator reads (ADR 0253; empty leaves the keeper's wording of the kind), and the row or fact it comes from (Source).
|
||||
// It is written as health.Finding.
|
||||
type Finding struct {
|
||||
Scope, ID, Token, Kind, Machine string
|
||||
Also []string
|
||||
Severity string
|
||||
Summary, Said string
|
||||
Headline, Explanation string
|
||||
Needs, Resolved string
|
||||
Source string
|
||||
Observer string
|
||||
Scope, ID, Ref, Kind, Machine string
|
||||
Also []string
|
||||
Severity string
|
||||
Summary, Said string
|
||||
Headline, Explanation string
|
||||
Needs, Resolved string
|
||||
Source string
|
||||
Observer string
|
||||
// Since is when what it says began, as the watchman saw it: the finding's time and its observation's, so the same
|
||||
// thing observed again writes the same bytes.
|
||||
Since time.Time
|
||||
@@ -54,11 +54,11 @@ func FindingKey(scope, id, token string) string { return health.FindingKey(scope
|
||||
|
||||
// Key is the finding's key: its token, or its kind when it has none.
|
||||
func (f Finding) Key() string {
|
||||
token := f.Token
|
||||
if token == "" {
|
||||
token = f.Kind
|
||||
ref := f.Ref
|
||||
if ref == "" {
|
||||
ref = f.Kind
|
||||
}
|
||||
return FindingKey(f.Scope, f.ID, token)
|
||||
return FindingKey(f.Scope, f.ID, ref)
|
||||
}
|
||||
|
||||
// EncodeFinding is f in mesh-sdk's layout, refusing one the keeper could not make a condition of.
|
||||
@@ -67,7 +67,7 @@ func EncodeFinding(f Finding) ([]byte, error) {
|
||||
if said == "" {
|
||||
said = f.Summary
|
||||
}
|
||||
return health.EncodeFinding(health.Finding{Key: f.Key(), Kind: f.Kind, Token: f.Token,
|
||||
return health.EncodeFinding(health.Finding{Key: f.Key(), Kind: f.Kind, Ref: f.Ref,
|
||||
Subject: health.Subject{Scope: f.Scope, ID: f.ID, Machine: f.Machine, Also: f.Also},
|
||||
Severity: health.Severity(f.Severity), Summary: f.Summary, Headline: f.Headline, Explanation: f.Explanation,
|
||||
Resolved: f.Resolved, Needs: f.Needs, Evidence: []health.Evidence{{At: f.Since.UTC(), Said: said}},
|
||||
@@ -80,7 +80,7 @@ func DecodeFinding(data []byte) (Finding, error) {
|
||||
if err != nil {
|
||||
return Finding{}, err
|
||||
}
|
||||
f := Finding{Scope: h.Subject.Scope, ID: h.Subject.ID, Token: h.Token, Kind: h.Kind, Machine: h.Subject.Machine,
|
||||
f := Finding{Scope: h.Subject.Scope, ID: h.Subject.ID, Ref: h.Ref, Kind: h.Kind, Machine: h.Subject.Machine,
|
||||
Also: h.Subject.Also, Severity: string(h.Severity), Summary: h.Summary, Headline: h.Headline, Explanation: h.Explanation,
|
||||
Needs: h.Needs, Resolved: h.Resolved, Source: h.Source, Observer: h.Observer, Since: h.At}
|
||||
if len(h.Evidence) > 0 {
|
||||
|
||||
@@ -396,7 +396,7 @@ func TestTheWatchmansFindings(t *testing.T) {
|
||||
|
||||
func seedUnhealthy(t *testing.T, w *Watchman, o *fakeOut, sev string, since time.Time) {
|
||||
t.Helper()
|
||||
f := Finding{Scope: ScopeModule, ID: "web.ace", Token: "unhealthy", Kind: KindUnhealthy, Machine: "ace", Severity: sev, Source: "health", Since: since, Summary: "web on ace is not healthy: x"}
|
||||
f := Finding{Scope: ScopeModule, ID: "web.ace", Ref: "unhealthy", Kind: KindUnhealthy, Machine: "ace", Severity: sev, Source: "health", Since: since, Summary: "web on ace is not healthy: x"}
|
||||
v, _ := EncodeFinding(f)
|
||||
o.findings[f.Key()], _ = DecodeFinding(v)
|
||||
w.SeedFinding(f.Key(), v)
|
||||
|
||||
@@ -143,7 +143,7 @@ func (w *Watchman) rewrittenOf(m string, p delivery.NetworkPart, all []delivery.
|
||||
if w.control[m] {
|
||||
severity = Urgent
|
||||
}
|
||||
return Finding{Scope: ScopeMachine, ID: id, Token: "rewritten", Kind: KindNetworkRewritten, Machine: m, Severity: severity,
|
||||
return Finding{Scope: ScopeMachine, ID: id, Ref: "rewritten", Kind: KindNetworkRewritten, Machine: m, Severity: severity,
|
||||
Source: "health-report", Since: p.Since, Summary: summary,
|
||||
Said: fmt.Sprintf("since %s: %s", stampSec(p.Since), strings.Join(said, " | "))}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func unitsOf(node string, u *delivery.UnitsHealth) (Finding, bool) {
|
||||
if len(u.Failed) > 1 {
|
||||
failed = fmt.Sprintf("%d failed services", len(u.Failed))
|
||||
}
|
||||
return Finding{Scope: ScopeMachine, ID: node, Token: "units", Kind: KindMachineUnits, Machine: node, Severity: Warning,
|
||||
return Finding{Scope: ScopeMachine, ID: node, Ref: "units", Kind: KindMachineUnits, Machine: node, Severity: Warning,
|
||||
Source: "health-report", Since: since,
|
||||
Summary: fmt.Sprintf("%s's service manager is degraded: %d failed unit(s) no module places — %s. Each is the machine's own: "+
|
||||
"mend or remove it there, or have a module place it", node, len(u.Failed), strings.Join(names, ", ")),
|
||||
@@ -388,7 +388,7 @@ func (w *Watchman) networks() {
|
||||
severity = Urgent
|
||||
summary += fmt.Sprintf("; %s cannot reach it", strings.Join(waiting, ", "))
|
||||
}
|
||||
f := Finding{Scope: ScopeMachine, ID: m, Token: "network", Kind: KindMachineNetwork, Machine: m, Severity: severity,
|
||||
f := Finding{Scope: ScopeMachine, ID: m, Ref: "network", Kind: KindMachineNetwork, Machine: m, Severity: severity,
|
||||
Source: "health-report", Since: w.health[m].Network.Since, Summary: summary, Said: strings.Join(said, " | ")}
|
||||
want[f.Key()] = true
|
||||
saysOwn[m] = true
|
||||
@@ -413,7 +413,7 @@ func (w *Watchman) networks() {
|
||||
if w.control[x] || slices.Contains(what, "the bus on it") {
|
||||
severity = Urgent
|
||||
}
|
||||
f := Finding{Scope: ScopeMachine, ID: x, Token: "unreachable", Kind: KindNetworkUnreachable, Machine: x, Also: from(x),
|
||||
f := Finding{Scope: ScopeMachine, ID: x, Ref: "unreachable", Kind: KindNetworkUnreachable, Machine: x, Also: from(x),
|
||||
Severity: severity, Source: "health-report",
|
||||
Summary: fmt.Sprintf("%s cannot be reached from %s: %s", x, strings.Join(from(x), ", "), strings.Join(what, ", ")),
|
||||
Said: strings.Join(said, " | ")}
|
||||
|
||||
@@ -350,13 +350,13 @@ func (w *Watchman) Stood(st Standing) {
|
||||
if st.Node != "" && st.Node != st.ProviderNode {
|
||||
also = []string{st.Node}
|
||||
}
|
||||
w.find(Finding{Scope: ScopeProvider, ID: id, Token: "failing", Kind: KindProviderFailing, Machine: st.ProviderNode, Also: also,
|
||||
w.find(Finding{Scope: ScopeProvider, ID: id, Ref: "failing", Kind: KindProviderFailing, Machine: st.ProviderNode, Also: also,
|
||||
Severity: Warning, Source: "provisioner.failing", Since: st.Since, Said: said,
|
||||
Summary: fmt.Sprintf("%s on %s keeps failing %s: %s, %d attempt(s) since %s", st.Module, st.ProviderNode, whom, class,
|
||||
st.Attempts, stamp(st.Since))})
|
||||
w.unfind(silentKey)
|
||||
now := w.now()
|
||||
w.signal("S8", id, ProviderSaysAgainWithin, signalDue{Do: dueFind, Finding: Finding{Scope: ScopeProvider, ID: id, Token: "silent",
|
||||
w.signal("S8", id, ProviderSaysAgainWithin, signalDue{Do: dueFind, Finding: Finding{Scope: ScopeProvider, ID: id, Ref: "silent",
|
||||
Kind: KindProviderSilent, Machine: st.ProviderNode, Severity: Warning, Source: "S8", Since: now,
|
||||
Summary: fmt.Sprintf("%s on %s said it keeps failing %s and has said nothing since %s: it stopped saying anything, so "+
|
||||
"its last word is all the mesh has", st.Module, st.ProviderNode, st.Consumer, stamp(now)),
|
||||
@@ -498,7 +498,7 @@ func (w *Watchman) haveFindings(key string, node, module string, h *delivery.Hav
|
||||
if h.Health == delivery.HealthFailed {
|
||||
if w.unhealthy[key] == 0 {
|
||||
w.unhealthy[key] = 1
|
||||
f := Finding{Scope: ScopeModule, ID: module + "." + node, Token: "unhealthy", Kind: KindUnhealthy, Machine: node,
|
||||
f := Finding{Scope: ScopeModule, ID: module + "." + node, Ref: "unhealthy", Kind: KindUnhealthy, Machine: node,
|
||||
Severity: Warning, Source: "health", Since: now,
|
||||
Summary: fmt.Sprintf("%s on %s is not healthy: %s", module, node, orWords(h.Detail, "its check failed")),
|
||||
Said: firstLine(orWords(h.Detail, "failed")),
|
||||
|
||||
Reference in New Issue
Block a user